/*
* 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.
*/
/*
* Executor.java
* Creation date: Jun 18, 2002
* By: rcypher
*/
package org.openquark.cal.internal.machine.g;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.regex.Pattern;
import org.openquark.cal.compiler.DataConstructor;
import org.openquark.cal.compiler.Expression;
import org.openquark.cal.compiler.ForeignFunctionInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.UnableToResolveForeignEntityException;
import org.openquark.cal.compiler.io.EntryPoint;
import org.openquark.cal.internal.machine.DynamicRuntimeEnvironment;
import org.openquark.cal.internal.machine.EntryPointImpl;
import org.openquark.cal.internal.runtime.ExecutionContextImpl;
import org.openquark.cal.machine.CALExecutor;
import org.openquark.cal.machine.MachineFunction;
import org.openquark.cal.machine.Program;
import org.openquark.cal.machine.StatsGenerator;
import org.openquark.cal.machine.StatsGenerator.StatsObject;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.runtime.CALExecutorException;
import org.openquark.cal.runtime.CalValue;
import org.openquark.cal.runtime.ExecutionContext;
import org.openquark.cal.runtime.ExecutionContextProperties;
import org.openquark.cal.runtime.ResourceAccess;
import org.openquark.cal.util.ArrayStack;
import org.openquark.util.UnsafeCast;
/**
* The Executor executes instruction streams.
* Creation date: Jun 18, 2002
* @author rcypher
*
*/
public class Executor implements CALExecutor {
/** State diagnostics. */
// Turn on execution diagnostics.
static boolean EXEC_DIAG = false; // At some point we will want to make this a final value.
static boolean RUNTIME_STATS = System.getProperty (GMachineConfiguration.RUNTIME_STATISTICS_PROP) != null;
static final boolean CALL_COUNTS = System.getProperty(GMachineConfiguration.CALL_COUNTS_PROP) != null;
// Turn on space use diagnostics.
static final boolean SPACE_DIAG = false;
private int maxStackSize = 0;
private int maxSimultaneousNodes = 0;
/** Executor state **/
// The program is the repository of defined functions.
final Program program;
/** Provides access to the resources of the current environment (e.g. from the workspace, or from Eclipse). */
final ResourceAccess resourceAccess;
// The stack used to represent the program graph.
GStack stack;
// The dump stack is used to hold suspended function evaluations.
private ArrayStack<DumpItem> dump;
// Macrocode
// Static instruction lists
private static final Instruction[] is_HaltTrap = {};
// The code itself
static Code m_HaltTrap = new Code(is_HaltTrap);
private static Code m_Eval = new Code (Instruction.I_Eval);
// The instruction pointer. This offsets into the currently executing
// instruction stream.
Instruction[] currentCode;
int currentOffset;
// Metrics
long iCount = 0;
private int nPushedArguments = 0;
// Output print value.
String printVal = "";
//boolean running = false;
// A collection of registered statistics generators.
private List<StatsGenerator> statsGenerators = new ArrayList<StatsGenerator> ();
/**
* Keep track of whether this executor was asked to quit or suspend.
* Used by the client to tell the executor to stop prematurely.
* Must be marked as volatile (or have access to it synchronized) even though read/writes of
* booleans are guaranteed to be atomic. See Effective Java item 48 pg 191.
*/
private volatile int continueAction = ACTION_CONTINUE;
private static final int ACTION_CONTINUE = 0;
private static final int ACTION_QUIT = 1;
/** Execution context associated with this executor. */
private ExecutionContextImpl executionContext;
/**
* Construct Executor from a Program and context.
* @param program
* @param resourceAccess
* the ResourceAccess instance to provide access to the resources of the current
* environment (e.g. from the workspace, or from Eclipse).
* @param context
*/
public Executor(Program program, ResourceAccess resourceAccess, ExecutionContext context) {
super();
stack = new GStack ();
dump = ArrayStack.make ();
this.program = program;
if (resourceAccess == null) {
throw new NullPointerException("g.Executor: resourceAccess can't be null.");
}
if (context == null) {
throw new NullPointerException("g.Executor: context can't be null.");
}
this.resourceAccess = resourceAccess;
this.executionContext = (GExecutionContext)context;
}
/** {@inheritDoc} */
public ExecutionContextImpl getContext () {
return executionContext;
}
/**
* Do the NOOP transition.
* This is used for diagnostics and instrumentation.
* @param object Object
*/
void i_instrument (StatsGenerator.ProfileObj object) {
if (SPACE_DIAG) {
if (object instanceof ExecTimeInfo) {
((ExecTimeInfo)object).nodeCount = 0;//Node.nodeOrdCount;
((ExecTimeInfo)object).maxSimultaneousNodes = maxSimultaneousNodes;
((ExecTimeInfo)object).maxStackSize = maxStackSize;
}
}
//System.out.println ("Instrument -> " + object.toString ());
for (final StatsGenerator gen : statsGenerators) {
gen.i_instrument(object);
}
}
/**
* Do a switch state transition.
* SWITCH does the following:
* - Pop a value from the value stack
* - Enter the code for the alternative, indexed by this value in the map
* @param inst The switch instruction currently being executed.
* @throws CALExecutorException
*/
void i_switchJ (Instruction inst) throws CALExecutorException {
Map<?, ?> jumpMap = inst.getMap();
try {
Node n = stack.peek ();
int tag = n.getOrdinalValue();
Integer jump = (Integer)jumpMap.get (Integer.valueOf(tag));
if (jump == null) {
//check for the wildcard pattern
jump = (Integer)jumpMap.get(Expression.Switch.SwitchAlt.WILDCARD_TAG);
if (jump == null) {
StringBuilder sb = new StringBuilder ();
String tagString;
if (n instanceof NConstr) {
tagString = ((NConstr)n).getTag().getName().getQualifiedName();
} else {
tagString = n.toString();
}
sb.append ("Unhandled case for " + tagString + ".");
//for example, if f x = case Nothing -> "abc";;
//then evaluating
//f (Just 'a')
//will trigger this pattern match exception
throw new CALExecutorException.ExternalException.PatternMatchFailure (((Instruction.I_SwitchJ) inst).getErrorInfo(), sb.toString(), null);
}
}
currentOffset += jump.intValue ();
} catch (ClassCastException e) {
throw new CALExecutorException.InternalException("i_switch: bad node type on stack.", e);
}
}
/**
* Do the ForeignSCCall state transition.
* Creation date: (May 3, 2002)
* @param foreignFunctionInfo ForeignFunctionInfo
* @throws CALExecutorException
*/
void i_foreignSCCall(ForeignFunctionInfo foreignFunctionInfo) throws CALExecutorException {
// NOTE: Generally speaking arguments to foreign functions need to be unboxed before the
// foreign function is invoked. However, some foreign functions take arguments of
// type CalValue. In this case the value should not be unboxed.
// When retrieving foreign argument values from the g-machine stack the
// functions getForeignArgument() and getForeignArguments() should be used.
// These will correctly handle unboxing the value.
try {
ForeignFunctionInfo.JavaKind kind = foreignFunctionInfo.getJavaKind();
if (kind.isMethod()) {
final ForeignFunctionInfo.Invocation invocationInfo = (ForeignFunctionInfo.Invocation)foreignFunctionInfo;
Method method = (Method)invocationInfo.getJavaProxy();
Object instanceObject = null;
int nJavaArgs = foreignFunctionInfo.getNArguments();
Object [] args;
Class<?>[] argTypes = method.getParameterTypes();
if (invocationInfo.getInvocationClass() != method.getDeclaringClass()) {
//if package scope class A defines public method f (static or non-static), and public class B extends A,
//we want to call B.f and not A.f. There is no way to do this via reflection, so we explicitly disable
//Java language access controls in this case.
method.setAccessible(true);
}
// NOTE: getForeignArguments() uses the argument type information to handle correctly
// unboxing the arguments to the foreign function. i.e. it doesn't unbox arguments
// of type CalObject .
if (kind.isStatic()){
args = getForeignArguments (nJavaArgs, argTypes);
} else {
args = getForeignArguments (nJavaArgs - 1, argTypes);
instanceObject = stack.pop ().getValue ();
}
Object result = method.invoke(instanceObject, args);
pushForeignResult (method.getReturnType(), result);
} else if (kind.isField()) {
final ForeignFunctionInfo.Invocation invocationInfo = (ForeignFunctionInfo.Invocation)foreignFunctionInfo;
Field field = (Field)invocationInfo.getJavaProxy();
if (invocationInfo.getInvocationClass() != field.getDeclaringClass()) {
//if package scope class A defines public field f (static or non-static), and public class B extends A,
//we want to call B.f and not A.f. There is no way to do this via reflection, so we explicitly disable
//Java language access controls in this case.
field.setAccessible(true);
}
Object result;
if (kind.isStatic()) {
result = field.get(null);
} else {
// NOTE: use getForeignArgument() to correctly handle unboxing.
Object instance = getForeignArgument(field.getDeclaringClass());
result = field.get(instance);
}
pushForeignResult (field.getType(), result);
} else if (kind.isConstructor()) {
// NOTE: As with a call to a foreign method the arguments to a Constructor can be of
// type CalValue. In this case the argument shouldn't be unboxed. This is handled by
// getArguments().
final ForeignFunctionInfo.Invocation invocationInfo = (ForeignFunctionInfo.Invocation)foreignFunctionInfo;
Constructor<?> constructor = (Constructor<?>)invocationInfo.getJavaProxy();
Class<?>[] argTypes = constructor.getParameterTypes();
Object result = constructor.newInstance(getForeignArguments(foreignFunctionInfo.getNArguments(), argTypes));
pushForeignResult (constructor.getDeclaringClass(), result);
} else if (kind.isCast()) {
final ForeignFunctionInfo.Cast castInfo = (ForeignFunctionInfo.Cast)foreignFunctionInfo;
if (kind == ForeignFunctionInfo.JavaKind.IDENTITY_CAST || kind == ForeignFunctionInfo.JavaKind.WIDENING_REFERENCE_CAST) {
//do nothing
} else if (kind == ForeignFunctionInfo.JavaKind.WIDENING_PRIMITIVE_CAST || kind == ForeignFunctionInfo.JavaKind.NARROWING_PRIMITIVE_CAST) {
performJavaPrimitiveCast (castInfo.getJavaReturnType(), getForeignArgument(castInfo.getJavaArgumentType(0)));
} else if (kind == ForeignFunctionInfo.JavaKind.NARROWING_REFERENCE_CAST) {
// We don't want' to pop the value off the stack in this case so
// we need to manually do the unboxing normally performed in
// getForeignArgument().
NValObject nvo = (NValObject)stack.peek();
Class<?> castResultType = castInfo.getJavaReturnType();
Class<?> argType = castInfo.getJavaArgumentType(0);
Object valueToCast;
if (argType.equals(CalValue.class)) {
valueToCast = nvo;
} else {
valueToCast = nvo.getValue();
}
if (!castResultType.isInstance(valueToCast)) {
throw new ClassCastException ("Cannot cast type " + nvo.getValue().getClass().getName() + " to type " + castResultType.getName());
}
} else {
throw new IllegalStateException("unrecognized cast kind " + kind);
}
} else if (kind.isInstanceOf()) {
final ForeignFunctionInfo.InstanceOf instanceOfInfo = (ForeignFunctionInfo.InstanceOf)foreignFunctionInfo;
// Make sure to use getForeignArgument() so that correct unboxing takes place.
final Object object = getForeignArgument(instanceOfInfo.getJavaArgumentType(0));
final Class<?> instanceOfType = instanceOfInfo.getInstanceOfType();
stack.pushBoolean(instanceOfType.isInstance(object));
} else if (kind.isNullLiteral()) {
stack.push (new NValObject(null));
} else if (kind.isNullCheck()) {
final ForeignFunctionInfo.NullCheck nullCheckInfo = (ForeignFunctionInfo.NullCheck)foreignFunctionInfo;
// Make sure to use getForeignArgument() so that correct unboxing takes place.
final Object object = getForeignArgument(nullCheckInfo.getJavaArgumentType(0));
stack.pushBoolean (nullCheckInfo.checkIsNull() ? object == null : object != null);
} else if (kind.isClassLiteral()) {
final ForeignFunctionInfo.ClassLiteral classLiteral = (ForeignFunctionInfo.ClassLiteral)foreignFunctionInfo;
stack.push(new NValObject(classLiteral.getReferentType()));
} else if (kind == ForeignFunctionInfo.JavaKind.NEW_ARRAY) {
final ForeignFunctionInfo.NewArray newArrayInfo = (ForeignFunctionInfo.NewArray)foreignFunctionInfo;
final int nSizeArgs = foreignFunctionInfo.getNArguments();
final Object newArray;
//we use the more efficient overload of newInstance in the case where only 1 sizing dimension is supplied.
if (nSizeArgs == 1) {
final int size = stack.popInt();
newArray = Array.newInstance(newArrayInfo.getComponentType(), size);
} else {
final int[] sizes = popIntArgs(nSizeArgs);
newArray = Array.newInstance(newArrayInfo.getComponentType(), sizes);
}
stack.push(new NValObject(newArray));
} else if (kind == ForeignFunctionInfo.JavaKind.LENGTH_ARRAY) {
//we know that object will be an array. However, it may be an int[] or an Object[] etc.
// Make sure to use getForeignArgument() so that correct unboxing takes place.
final Object object = getForeignArgument(foreignFunctionInfo.getJavaArgumentType(0));
stack.pushInt(Array.getLength(object));
} else if (kind == ForeignFunctionInfo.JavaKind.SUBSCRIPT_ARRAY) {
final int nSubscriptArgs = foreignFunctionInfo.getNArguments() - 1;
final Object result;
if (nSubscriptArgs == 1) {
final int index = stack.popInt();
// Make sure to use getForeignArgument() so that correct unboxing takes place.
final Object array = getForeignArgument(foreignFunctionInfo.getJavaArgumentType(0));
result = Array.get(array, index);
} else {
final int[] indices = popIntArgs(nSubscriptArgs);
// Make sure to use getForeignArgument() so that correct unboxing takes place.
final Object array = getForeignArgument(foreignFunctionInfo.getJavaArgumentType(0));
Object subscriptedArray = array;
for (int i = 0; i < nSubscriptArgs; ++i) {
subscriptedArray = Array.get(subscriptedArray, indices[i]);
}
result = subscriptedArray;
}
pushForeignResult(foreignFunctionInfo.getJavaReturnType(), result);
} else if (kind == ForeignFunctionInfo.JavaKind.UPDATE_ARRAY) {
final int nSubscriptArgs = foreignFunctionInfo.getNArguments() - 2;
// Make sure to use getForeignArgument() so that correct unboxing takes place.
final Object element = getForeignArgument(foreignFunctionInfo.getJavaArgumentType(nSubscriptArgs+1));
if (nSubscriptArgs == 1) {
int index = stack.popInt();
// Since this object will always be an array, and never an instance of CalValue, we can simply pop
// the element and call 'getValue()'.
Object array = stack.pop().getValue();
Array.set(array, index, element);
} else {
int[] indices = popIntArgs(nSubscriptArgs);
// Since this object will always be an array, and never an instance of CalValue, we can simply pop
// the element and call 'getValue()'.
Object array = stack.pop().getValue();
Object subscriptedArray = array;
for (int i = 0; i < nSubscriptArgs - 1; ++i) {
subscriptedArray = Array.get(subscriptedArray, indices[i]);
}
Array.set(subscriptedArray, indices[nSubscriptArgs - 1], element);
}
pushForeignResult(void.class, null);
} else {
throw new IllegalStateException("unrecognized kind " + kind);
}
} catch (java.lang.reflect.InvocationTargetException e) {
Throwable targetException = e.getCause();
throw new CALExecutorException.ExternalException.ForeignOrPrimitiveFunctionException("The exception " + targetException.getClass().getName() + " occurred while calling " + foreignFunctionInfo.getCalName() + ".", targetException);
} catch (IllegalAccessException e) {
throw new CALExecutorException.ExternalException.ForeignOrPrimitiveFunctionException("The foreign function " + foreignFunctionInfo.getCalName() + " is not accessible.", e);
} catch (InstantiationException e) {
throw new CALExecutorException.ExternalException.ForeignOrPrimitiveFunctionException("An exception occurred while calling " + foreignFunctionInfo.getCalName() + ".", e);
} catch (ClassCastException e) {
throw new CALExecutorException.ExternalException.ForeignOrPrimitiveFunctionException("An exception occurred while calling " + foreignFunctionInfo.getCalName() + ".", e);
} catch (UnableToResolveForeignEntityException e) {
throw new CALExecutorException.ExternalException.ForeignOrPrimitiveFunctionException("An exception occurred while calling " + foreignFunctionInfo.getCalName() + ".", e);
} catch (Exception e) {
throw new CALExecutorException.ExternalException.ForeignOrPrimitiveFunctionException("An exception occurred while calling " + foreignFunctionInfo.getCalName() + ".", e);
}
}
/**
* Handles the various primitive casts allowed by Java as specified in section 2.6 of the JVM spec.
* Note: it is assumed that this method is not called for identity casts, or other cast types such
* as reference casts.
* @param returnType
* @param valueToCast
*/
private void performJavaPrimitiveCast (Class<?> returnType, Object valueToCast) {
if (returnType == int.class) {
if (valueToCast instanceof Number) {
stack.pushInt(((Number)valueToCast).intValue());
} else {
stack.pushInt((int)((Character)valueToCast).charValue());
}
} else if (returnType == double.class) {
if (valueToCast instanceof Number) {
stack.pushDouble(((Number)valueToCast).doubleValue());
} else {
stack.pushDouble((double)((Character)valueToCast).charValue());
}
} else if (returnType == char.class) {
stack.pushChar((char)((Number)valueToCast).intValue());
} else if (returnType == long.class) {
if (valueToCast instanceof Number) {
stack.pushLong(((Number)valueToCast).longValue());
} else {
stack.pushLong((long)((Character)valueToCast).charValue());
}
} else if (returnType == byte.class) {
if (valueToCast instanceof Number) {
stack.pushByte(((Number)valueToCast).byteValue());
} else {
stack.pushByte((byte)((Character)valueToCast).charValue());
}
} else if (returnType == short.class) {
if (valueToCast instanceof Number) {
stack.pushShort(((Number)valueToCast).shortValue());
} else {
stack.pushShort((short)((Character)valueToCast).charValue());
}
} else if (returnType == float.class) {
if (valueToCast instanceof Number) {
stack.pushFloat(((Number)valueToCast).floatValue());
} else {
stack.pushFloat((float)((Character)valueToCast).charValue());
}
} else {
throw new IllegalArgumentException("unexpected Java primitive cast type");
}
}
/**
* Wrap the result of a foreign call in the appropriate node type
* and push onto stack.
* @param returnType
* @param result
*/
private void pushForeignResult (Class<?> returnType, Object result) {
if (result == null && (returnType == Void.TYPE || returnType.getName().equals("void"))) {
// We treat Unit as an enum data type so it is simply the int value of zero.
stack.push(new NValInt(0));
return;
}
if (returnType == boolean.class) {
stack.pushBoolean(((Boolean)result).booleanValue());
} else
if (returnType == double.class) {
stack.pushDouble (((Double)result).doubleValue());
} else
if (returnType == char.class) {
stack.pushChar (((Character)result).charValue());
} else
if (returnType == int.class) {
stack.pushInt (((Integer)result).intValue ());
} else
if (returnType == long.class) {
stack.pushLong (((Long)result).longValue());
} else
if (returnType == short.class) {
stack.pushShort(((Short)result).shortValue());
} else
if (returnType == byte.class) {
stack.pushByte (((Byte)result).byteValue());
} else
if (returnType == float.class) {
stack.pushFloat (((Float)result).floatValue());
} else
if (returnType == CalValue.class) {
stack.push ((Node)result);
} else {
stack.push (new NValObject(result));
}
}
/**
* Creation date: (June 28, 2002)
* @param nArgs number of arguments to pop of the value stack
* @param argTypes
* @return Object[] popped arguments wrapped in an array
*/
private Object[] getForeignArguments (int nArgs, Class<?>[] argTypes) {
// Note: arguments on stack are in reverse to the order that they are
// expected.
Object[] arguments = new Object [nArgs];
for (int i = 0; i < nArgs; ++i) {
arguments[nArgs-i-1] = getForeignArgument(argTypes[nArgs-i-1]);
}
return arguments;
}
/**
* Retrieve a foreign argument off the top of the stack.
* The argType is used to perform any necessary unboxing
* of the value.
* @param argType
* @return the foreign argument value.
*/
private Object getForeignArgument(Class<?> argType) {
Node n = stack.pop();
Object argValue = n;
if (argType != CalValue.class) {
if (n instanceof NVal) {
argValue = n.getValue ();
}
if (argValue instanceof DataConstructor) {
if (((DataConstructor)argValue).getName().equals(CAL_Prelude.TypeConstructors.CalValue)) {
argValue = n;
}
}
}
return argValue;
}
/**
* @param nArgs
* @return pops nArgs int args from the stack, putting them into an int array, and in the expected order (i.e. the reverse of
* stack order)
*/
private int[] popIntArgs (int nArgs) {
// Note: arguments on stack are in reverse to the order that they are
// expected.
int[] arguments = new int [nArgs];
for (int i = 0; i < nArgs; ++i) {
arguments [nArgs - i - 1] = stack.popInt();
}
return arguments;
}
void executeInstruction (Instruction inst) throws CALExecutorException {
switch (inst.getTag()) {
case Instruction.T_Alloc:
for (int i = 0; i < inst.getN(); ++i) {
stack.push (new NInd ());
}
break;
case Instruction.T_Call:
int n = inst.getN();
pushDumpItem (n + 2);
stack.peek ().i_dispatch (this, n);
break;
case Instruction.T_CondJ:
if (stack.popBoolean()) {
currentOffset += inst.getN();
}
break;
case Instruction.T_Dispatch:
stack.peek().i_dispatch (this, inst.getN());
break;
case Instruction.T_ForeignFunctionCall:
i_foreignSCCall((ForeignFunctionInfo)inst.getInfo());
break;
case Instruction.T_ClearStack:
stack.clear ();
break;
case Instruction.T_Eval:
stack.peek().i_eval(this);
break;
case Instruction.T_Instrument:
Object object = ((Instruction.I_Instrument)inst).getInfo();
if (object instanceof ExecTimeInfo) {
((ExecTimeInfo)object).instructionCount = getInstructionCount();
if (SPACE_DIAG) {
((ExecTimeInfo)object).nodeCount = 0;//Node.nodeOrdCount;
((ExecTimeInfo)object).maxSimultaneousNodes = maxSimultaneousNodes;
((ExecTimeInfo)object).maxStackSize = maxStackSize;
}
}
//System.out.println ("Instrument -> " + object.toString ());
for (final StatsGenerator gen : statsGenerators) {
gen.i_instrument((StatsGenerator.ProfileObj)object);
}
break;
case Instruction.T_PushFalse:
stack.pushBoolean (false);
//stack.push (NValBoolean.FALSE);
break;
case Instruction.T_PushTrue:
stack.pushBoolean (true);
//stack.push (NValBoolean.TRUE);
break;
case Instruction.T_Unwind:
stack.peek().i_unwind(this);
break;
case Instruction.T_Jump:
currentOffset += inst.getN();
break;
case Instruction.T_MkapN:
Node n1 = stack.pop ();
for (int i = 0; i < inst.getN(); ++i) {
Node n2 = stack.pop ();
Node nap = n1.apply (n2);//new NAp (n1, n2);
n1 = nap;
}
stack.push (n1);
break;
case Instruction.T_PackCons:
int arity = inst.getArity();
int ord = inst.getOrd();
DataConstructor tag = (DataConstructor)inst.getInfo();
if (CALL_COUNTS) {
i_instrument (new Executor.CallCountInfo((tag).getName(), "DataConstructor instance counts"));
}
Node [] nodes = new Node [arity];
for (int i = 0; i < arity; ++i) {
nodes [i] = stack.pop ().getLeafNode();
}
Node nc = new NConstrGeneral (tag, ord, nodes);
stack.push (nc);
break;
case Instruction.T_PackCons0:
ord = inst.getOrd();
tag = (DataConstructor)inst.getInfo();
if (CALL_COUNTS) {
i_instrument (new Executor.CallCountInfo((tag).getName(), "DataConstructor instance counts"));
}
stack.push (new NConstr0(tag, ord));
break;
case Instruction.T_PackCons2:
ord = inst.getOrd();
tag = (DataConstructor)inst.getInfo();
if (CALL_COUNTS) {
i_instrument (new Executor.CallCountInfo((tag).getName(), "DataConstructor instance counts"));
}
Node n0 = stack.pop ();
n1 = stack.pop ();
nc = new NConstr2(tag, ord, n0, n1);
stack.push (nc);
break;
case Instruction.T_Pop:
n = inst.getN();
for (int i = 0; i < n; ++i) {
stack.pop ();
}
break;
case Instruction.T_PrimOp:
try {
((PrimOp)inst.getInfo()).perform(this);
} catch (ClassCastException e1) {
throw new CALExecutorException.InternalException ("PRIMOP: Bad argument type on stack for subcode <" + ((PrimOp)inst.getInfo()).getDescription () + ">", e1);
} catch (java.util.EmptyStackException e1) {
throw new CALExecutorException.InternalException("PRIMOP: Too few value arguments on stack for subcode <" + ((PrimOp)inst.getInfo()).getDescription () + ">", e1);
} catch (java.lang.ArithmeticException e1) {
throw new CALExecutorException.ExternalException.ForeignOrPrimitiveFunctionException("PRIMOP: Arithmetic exception for subcode <" + ((PrimOp)inst.getInfo()).getDescription () + ">", e1);
}
break;
case Instruction.T_Push:
stack.push (stack.get (stack.size () - 1 - inst.getN()));
break;
case Instruction.T_PushGlobal:
QualifiedName name = inst.getName();
try {
GMachineFunction cl = (GMachineFunction)program.getCodeLabel(name);
if (cl != null) {
NGlobal ng = cl.makeNGlobal(executionContext);
stack.push (ng);
}
} catch (Program.ProgramException e) {
throw new CALExecutorException.InternalException ("Push Global: " + name + ": ", e);
}
break;
case Instruction.T_PushGlobalN:
stack.push (inst.getGlobalNode());
break;
case Instruction.T_PushPrimitiveNode:
stack.push (inst.getNode());
break;
case Instruction.T_PushList:
stack.push (inst.getNode());
break;
case Instruction.T_PushString:
stack.push (inst.getNode());
break;
case Instruction.T_PushVVal:
stack.push (inst.getNode());
break;
case Instruction.T_SelectDCField: {
Node dcField = stack.peek().i_selectDCField((DataConstructor)inst.getInfo(), inst.getN(), ((Instruction.I_SelectDCField)inst).getErrorInfo());
if (dcField != null) {
int updatePosition = stack.size () - 1;
stack.set(updatePosition, dcField);
}
}
break;
case Instruction.T_LazySelectDCField: {
// create a lazy dc field selection node
Node dcExpression = stack.pop ();
Node lazyDCFieldSelection = new NDataConsFieldSelection(dcExpression, (DataConstructor)inst.getInfo(), inst.getN(), ((Instruction.I_LazySelectDCField)inst).getErrorInfo());
stack.push (lazyDCFieldSelection);
}
break;
case Instruction.T_Slide:
n = inst.getN();
Node o = stack.pop ();
for (int i = 0; i < n; ++i) {
stack.pop ();
}
stack.push (o);
break;
case Instruction.T_Split:
stack.peek ().i_split (this, inst.getN());
break;
case Instruction.T_Squeeze:
stack.squeeze (inst.getX(), inst.getY());
break;
case Instruction.T_Switch:
break;
case Instruction.T_SwitchJ:
i_switchJ (inst);
break;
case Instruction.T_Update:
Node top = stack.pop ();
int updatePosition = stack.size () - 1 - inst.getN();
stack.get (updatePosition).setIndirectionNode(top);
stack.set(updatePosition, top);
break;
// Create a new record node and push it onto the stack.
case Instruction.T_CreateRecord:
Node recordVal = new NRecordValue (inst.getN());
stack.push (recordVal);
break;
// Modify the record on top of the stack. It is
// safe to modify the actual node because this instruction
// only occurs as part of record creation/extension.
case Instruction.T_PutRecordField:
String fieldName = (String)inst.getInfo();
Node fieldVal = stack.pop ();
NRecordValue record = (NRecordValue)stack.peek();
record.putValue(fieldName, fieldVal);
break;
// Replace the record on top of the stack with a copy
// that can be modified.
case Instruction.T_ExtendRecord:
o = stack.pop();
record = (NRecordValue)o;
record = new NRecordValue (record);
stack.push (record);
break;
// Replace the record value on top of the stack with
// the value of a field in the record.
case Instruction.T_RecordSelection:
fieldName = (String)inst.getInfo();
record = (NRecordValue)stack.pop();
stack.push(record.getValue(fieldName));
break;
// Create a node representing the
// application of record selection.
case Instruction.T_LazyRecordSelection:
fieldName = (String)inst.getInfo();
o = stack.pop();
stack.push (new NRecordSelection(fieldName, o));
break;
case Instruction.T_LazyRecordUpdate:
{
o = stack.pop();
record = new NRecordUpdate(o);
stack.push (record);
break;
}
// Create a node representing the application of
// record extension
case Instruction.T_LazyRecordExtension:
o = stack.pop ();
record = new NRecordExtension (o);
stack.push (record);
break;
// Remove a field value from the record node on top
// of the stack. It is safe to modify the actual node
// as this only happens as part of creating a new record instance.
case Instruction.T_RemoveRecordField:
{
fieldName = (String)inst.getInfo();
record = (NRecordValue)stack.peek();
record.removeValue(fieldName);
break;
}
case Instruction.T_Println:
System.out.println(inst.getInfo().toString());
break;
case Instruction.T_CreateList:
stack.push (new NValObject (new ArrayList<Object>()));
break;
case Instruction.T_PutListValue:
{
Node listNode = stack.pop ();
Object listValue = listNode;
if (listNode instanceof NVal) {
listValue = listNode.getValue();
}
List<Object> fl = UnsafeCast.unsafeCast(stack.peek().getValue());
fl.add(listValue);
break;
}
case Instruction.T_Cast:
{
i_Cast((Class<?>)inst.getInfo());
break;
}
case Instruction.T_DebugProcessing:
{
// If tracing is turned on we want to generate a trace message for
// the named function.
QualifiedName functionName = inst.getName();
if (executionContext.isDebugProcessingNeeded(functionName.getQualifiedName())) {
arity = inst.getArity();
Node args[] = new Node[arity];
// The arguments are on the top of the stack.
for (int i = 0; i < arity; ++i) {
args[i] = stack.get(stack.size() - i - 1);
}
executionContext.debugProcessing(functionName.getQualifiedName(), args);
}
break;
}
default:
//programming error. Instruction not added to the list above.
throw new IllegalStateException();
}
}
private final void i_Cast (Class<?> castType) {
NValObject nvo = (NValObject)stack.peek();
if (!castType.isInstance(nvo.getValue())) {
throw new ClassCastException ("Cannot cast type " + nvo.getValue().getClass().getName() + " to type " + castType.getName());
}
}
/**
* Execute the program at the IP
* Creation date: (3/3/00 3:57:46 PM)
* @param c
* @throws CALExecutorException
*/
void exec(Code c) throws CALExecutorException {
setIP (c);
// preallocate exception for VM errors, since in this case, we may not be able to allocate more memory.
CALExecutorException.InternalException.VMException vmException = CALExecutorException.InternalException.VMException.makeInitial();
// Continue a fetch-execute cycle until halted by an exception
try {
for (;;) {
if (continueAction != ACTION_CONTINUE) {
if (continueAction == ACTION_QUIT) {
throw (new CALExecutorException.ExternalException.TerminatedByClientException("Evaluation halted by client.", null));
}
}
if (currentOffset >= currentCode.length) {
setIP(m_HaltTrap);
return;
}
Instruction inst = currentCode [currentOffset++];
executeInstruction (inst);
iCount++;
if (EXEC_DIAG) {
showState ();
}
if (SPACE_DIAG) {
calcSpaceUsage ();
}
}
} catch (NullPointerException e) {
// Illegal 'address' (i.e. ip was null)
throw new CALExecutorException.InternalException("Illegal address trap: IP was 0", e);
} catch (CALExecutorException e) {
throw e;
} catch (Error e) {
// Assume that something really bad has happened: reset the state of the
// machine and force a finalization/gc.
reset ();
for (int i = 0; i < 2; ++i) {
//System.out.println ("trying to free memory " + i);
System.runFinalization();
System.gc();
}
vmException.initCause(e);
throw vmException;
} catch (Exception e) {
throw new CALExecutorException.InternalException("An Exception was encountered: ", e);
}
}
/**
* Run the function specified by the EntryPoint using the given Java arguments.
* @param entryPoint
* @param arguments (null is accepted and used as the 0-length Object array).
* @return the resulting value
* @throws CALExecutorException
*/
public Object exec(EntryPoint entryPoint, Object[] arguments) throws CALExecutorException {
reset ();
if (entryPoint == null) {
throw new CALExecutorException.InternalException ("null EntryPoint.", null);
}
final QualifiedName entryPointSCName = ((EntryPointImpl)entryPoint).getFunctionName();
if (arguments != null) {
nPushedArguments = arguments.length;
for (int i = nPushedArguments - 1; i >= 0; --i) {
stack.push (new NValObject (arguments[i]));
}
} else {
nPushedArguments = 0;
}
Code c = setupStartPoint2 (entryPointSCName);
exec (c);
// Clear the program field of the execution context. This prevents
// an out-of date instance of Program from being held.
((GExecutionContext)executionContext).clearProgram();
return stack.peek ().getValue ();
}
/**
* Set the machine's state to start executing the named SC assuming that the pushed arguments are on the stack.
* @param entryPointSCName
* @return Code
* @throws CALExecutorException
*/
Code setupStartPoint2 (QualifiedName entryPointSCName) throws CALExecutorException {
try {
GProgram.GModule startModule = (GProgram.GModule)program.getModule(entryPointSCName.getModuleName());
MachineFunction mf = startModule.getFunction(entryPointSCName);
// The specified target may be an alias of another function, or it may be defined as a literal value
// (either directly or from following an alias chain). In either case there is no source/class
// generated. So we need to either return the literal value or follow the alias to a
// function for which there is a generated source/class file.
if (mf.getLiteralValue() != null) {
Instruction[] enterInst = new Instruction [4];
enterInst [0] = new Instruction.I_Instrument (new ExecTimeInfo(true, getInstructionCount()));
enterInst [1] = Instruction.I_PushVVal.makePushVVal(mf.getLiteralValue());
enterInst [2] = Instruction.I_Eval;
enterInst [3] = new Instruction.I_Instrument (new ExecTimeInfo(false, getInstructionCount()));
Code enterCode = new Code (enterInst);
return (enterCode);
}
if (mf.getAliasOf() != null) {
entryPointSCName = mf.getAliasOf();
}
if (nPushedArguments != 0) {
Instruction[] enterInst = new Instruction [nPushedArguments + 3];
enterInst [0] = new Instruction.I_PushGlobal (entryPointSCName);
for (int i = 1; i <= nPushedArguments; i++) {
enterInst [i] = new Instruction.I_MkapN (1);
}
enterInst [nPushedArguments + 1] = Instruction.I_Eval;
enterInst [nPushedArguments + 2] = new Instruction.I_Instrument (new ExecTimeInfo (false, getInstructionCount()));
Instruction iStartInst[] = new Instruction [1];
iStartInst [0] = new Instruction.I_Instrument (new ExecTimeInfo (true, getInstructionCount()));
InstructionList il = new InstructionList (iStartInst);
il.code (enterInst);
Code istart = new Code (il);
return (istart);
}
// No supplied args. We should be executing a CAF.
// Check to be sure and throw an error if necessary.
GMachineFunction cl = (GMachineFunction)program.getCodeLabel(entryPointSCName);
NGlobal ng = cl.makeNGlobal(executionContext);
int arity = ng.getArity();
if (arity != 0) {
throw new CALExecutorException.InternalException("Expecting arguments but didn't get any.", null);
}
Instruction[] enterInst = new Instruction [4];
enterInst [0] = new Instruction.I_Instrument (new ExecTimeInfo(true, getInstructionCount()));
enterInst [1] = new Instruction.I_PushGlobal (entryPointSCName);
enterInst [2] = Instruction.I_Eval;
enterInst [3] = new Instruction.I_Instrument (new ExecTimeInfo(false, getInstructionCount()));
Code enterCode = new Code (enterInst);
return (enterCode);
} catch (Program.ProgramException e) {
throw new CALExecutorException.InternalException("Unable to fetch Global '" + entryPointSCName + "': ", e);
}
}
/**
* Reset the executor
* This will perform a warm-restart, clearing out all critical runtime states,
* such that the runtime can be reused without interference of the previous
* session in the subsequent session.
* NOTE: The GlobalFrame and Program are not cleared. Reset is designed to allow
* a Program to be re-executed in various ways. During execution the Program code
* becomes 'attuned' to the developing GlobalFrame by code rewriting optimizations
* and this makes it necessary to preserve this too.
* Creation date: (4/6/01 8:35:37 PM)
*/
void reset() {
continueAction = ACTION_CONTINUE;
stack = new GStack ();
dump = ArrayStack.make ();
// Reset the IP to the start
setIP (m_HaltTrap);
iCount = 0;
maxStackSize = 0;
((GExecutionContext)executionContext).reset ((GProgram)program, resourceAccess);
}
/**
* Return the number of instructions run since the count was last reset.
* Creation date: (4/4/00 6:39:54 PM)
* @return long the number of instructions
*/
long getInstructionCount() {
return iCount;
}
/**
* Ask the runtime to quit.
* Note that you can only ask the runtime to quit; you can't un-quit - we may want to have something like this later
* if we want to implement "restart."
* Creation date: (3/21/02 1:39:40 PM)
*/
public void requestQuit() {
continueAction = ACTION_QUIT;
}
public void requestSuspend () {
executionContext.setStepAllThreads(true);
executionContext.setStepping(true);
}
/**
* Set the regular expression that the tracing code uses to filter the traces.
*
* @param patterns The list of regular expressions to use when filtering traces.
*/
public void setTraceFilters(List<Pattern> patterns){
executionContext.setTraceFilters(patterns);
}
/**
* showState()
* Sends a description of the g-machines internal state to
* the message dispatcher.
* Original Author: rcypher
*/
void showState () {
StringBuilder sb = new StringBuilder ();
sb.append(iCount + ") " + printVal + "\n");
sb.append ("Stack [\n");
sb.append (showStack (7));
sb.append (" ]\n");
sb.append (showDump (7) + "\n");
sb.append ("Code:\n");
for (int i = currentOffset; i < currentCode.length; ++i) {
sb.append(" ");
sb.append(currentCode[i].toString());
sb.append ("\n");
}
//System.out.println(sb.toString());
CodeGenerator.MACHINE_LOGGER.log(Level.FINE, sb.toString());
}
/**
* showStack()
* Creates a string showing the state of the stack.
* the message dispatcher.
*
* Original Author: rcypher
* @param indent int The number of spaces to indent the stack description.
* @return A string containing the stack description.
*/
String showStack (int indent) {
StringBuilder sb = new StringBuilder ();
sb.append ("stack size = " + stack.size () + "\n");
for (int i = 0; i < stack.size (); ++i) {
Node n = stack.get (i);
sb.append (n.toString (indent + 4) + "\n");
}
return sb.toString ();
}
/**
* showDump()
* Create a string containing a description of the dump stack.
* Original Author: rcypher
* @param indent int The number of spaces to indent the dump stack description.
* @return A string containing the dump stack description.
*/
String showDump (int indent) {
StringBuilder sb = new StringBuilder ();
sb.append("Dump(" + dump.size() +"): \n");
for (int i = dump.size () - 1; i >= 0; --i) {
DumpItem di = dump.get (i);
sb.append (di.toString (indent));
sb.append ("\n");
}
return sb.toString ();
}
/**
* Register a stats generator.
* @param gen
*/
public void addStatsGenerator (StatsGenerator gen) {
if (!statsGenerators.contains (gen)) {
statsGenerators.add (gen);
}
}
/**
* Remove a stats generator.
* @param gen
*/
public void removeStatsGenerator (StatsGenerator gen) {
statsGenerators.remove (gen);
}
public static void setExecDiag (boolean b) {
EXEC_DIAG = b;
}
void calcSpaceUsage () {
if (iCount < 1000 || (iCount % 10000) == 0) {
// Determine the number of existing nodes.
List<Node> v = new ArrayList<Node> ();
for (int j = 0; j < stack.getFullStack().size (); ++j) {
Node n = stack.getFullStack().get (j);
if (!v.contains (n)) {
v.add (n);
n.addChildren (v);
}
}
if (v.size () > maxSimultaneousNodes) {
maxSimultaneousNodes = v.size ();
//System.out.println ("max stack size = " + maxStackSize);
}
}
if (stack.stack.size() > maxStackSize) {
maxStackSize = stack.stack.size ();
}
}
/**
* Pop an item off of the dump stack, while
* preserving the top item on the current stack.
*/
void popDumpItem () {
DumpItem di = dump.pop ();
Node o = stack.pop ();
stack.unSegment(di.stackBottom);
stack.push (o);
setIP (di.getCode(), di.getOffset());
}
/**
* Pop an item off the dump stack, while
* preserving the bottom item of the
* current stack.
*/
void popDumpItemBottom () {
DumpItem di = dump.pop ();
Node o = stack.get (0);
stack.unSegment(di.stackBottom);
stack.push (o);
setIP (di.getCode(), di.getOffset());
}
/**
* Push a dump item and start a new stack with the
* top of the existing stack.
*/
void pushDumpItem () {
dump.push(new DumpItem(stack.segmentOffset(1), currentCode, currentOffset));
}
void pushDumpItem (int n) {
dump.push(new DumpItem(stack.segmentOffset(n), currentCode, currentOffset));
}
int getDumpSize () {
return dump.size ();
}
void setIP (Instruction[] code, int offset) {
currentCode = code;
currentOffset = offset;
}
void setIP (Code code) {
currentCode = code.getInstructions();
currentOffset = 0;
}
/*
* Note: This function is public so that classes in the g.functions package can use it.
* It should not be used directly by client code!
*/
public final Node internalEvaluate (Node n) throws CALExecutorException {
// Create a new Executor instance using the same program and execution context.
Executor executor = new Executor(this.program, resourceAccess, this.executionContext);
// Push the node to be evaluated onto the stack and start execution.
executor.stack.push(n);
executor.exec(m_Eval);
Node result = executor.stack.peek();
return result.getLeafNode();
}
/**
* @return the Program associated with this Executor instance
*/
public Program getProgram () {return program;}
static final class DumpItem {
private final Instruction[] code;
private final int offset;
final int stackBottom;
DumpItem (int stackBottom, Instruction[] code, int offset) {
this.stackBottom = stackBottom;
this.code = code;
this.offset = offset;
}
public final int getStackBottom () {
return stackBottom;
}
public final int getOffset () {
return offset;
}
public final Instruction[] getCode () {
return code;
}
@Override
public final String toString () {
return toString (0);
}
public final String toString (int indent) {
StringBuilder sp = new StringBuilder ();
for (int i = 0; i < indent; ++i) {
sp.append (" ");
}
StringBuilder sb = new StringBuilder ();
sb.append (sp.toString ());
sb.append ("c: ");
// Offset no = ip.newOffset(ip.getIP());
// for (int i = 0; i < 3; ++i) {
// try {
// Instruction in = (Instruction)no.fetch ();
// sb.append (in.toString ().substring(0, 20));
// } catch (Exception e) {
// break;
// }
// }
sb.append (" s: ");
// for (int i = 0; i < stack.size (); i++) {
// sb.append (" #" + ((Node)stack.get (i)).ord);
// }
return sb.toString ();
}
}
static final class MArrayStack extends ArrayStack<Node> {
private static final long serialVersionUID = -2647651286807650427L;
void rr (int start, int end) {
removeRange (start, end);
}
}
static final class GStack {
private int bottom = 0;
MArrayStack stack = new MArrayStack ();
public Node pop () throws EmptyStackException {
if (stack.size () <= bottom) {
throw new EmptyStackException ();
}
return stack.pop ();
}
public boolean popBoolean() throws EmptyStackException {
return ((NValBoolean)pop()).getBooleanValue();
}
public char popChar() throws EmptyStackException {
return ((NValChar)pop()).getCharValue();
}
public double popDouble() throws EmptyStackException {
return ((NValDouble)pop()).getDoubleValue();
}
public int popInt() throws EmptyStackException {
return ((NValInt)pop()).getIntValue();
}
public long popLong() throws EmptyStackException {
return ((NValLong)pop()).getLongValue();
}
public short popShort() throws EmptyStackException {
return ((NValShort)pop()).getShortValue();
}
public byte popByte() throws EmptyStackException {
return ((NValByte)pop()).getByteValue();
}
public float popFloat() throws EmptyStackException {
return ((NValFloat)pop()).getFloatValue();
}
public String popString () throws EmptyStackException {
return (String)((NValObject)pop()).getValue();
}
public Node peek () throws EmptyStackException {
if (stack.size () <= bottom) {
throw new EmptyStackException ();
}
return stack.peek ();
}
public int size () {
return (stack.size () - bottom);
}
public boolean empty () {
return stack.size() <= bottom;
}
public int segment () {
int b = bottom;
bottom = stack.size ();
return b;
}
public int segmentOffset (int offset) {
int b = bottom;
bottom = stack.size() - offset;
return b;
}
public int getBottom () {
return bottom;
}
public Node get (int index) throws ArrayIndexOutOfBoundsException {
return stack.get (index + bottom);
}
public void add (int index, Node obj) {
stack.add (index + bottom, obj);
}
public void push (Node item) {
stack.push (item.getLeafNode());
}
public void pushBoolean(boolean b) {
push (new NValBoolean (b));
}
public void pushChar(char c) {
push (new NValChar(c));
}
public void pushDouble(double d) {
push (new NValDouble(d));
}
public void pushInt(int i) {
push (new NValInt(i));
}
public void pushLong(long d) {
push (new NValLong(d));
}
public void pushByte(byte b) {
push (new NValByte(b));
}
public void pushShort(short s) {
push (new NValShort(s));
}
public void pushFloat(float f) {
push (new NValFloat(f));
}
public void pushString(String s) {
push (new NValObject(s));
}
public void clear () {
for (int n = stack.size() - bottom; n > 0; --n) {
stack.pop();
}
//this pattern doesn't seem to help, but it is cute.
//stack.subList(bottom, stack.size()).clear();
}
public boolean isEmpty () {
return stack.size () <= bottom;
}
public Node set (int index, Node element) throws ArrayIndexOutOfBoundsException {
return stack.set (index + bottom, element);
}
public void unSegment (int n) {
clear ();
bottom = n;
}
MArrayStack getFullStack() {
return stack;
}
public void remove (int i) {
stack.remove (i+bottom);
}
public void squeeze (int x, int y) {
int start = stack.size() - (x + y);
int end = start + y;
stack.rr (start, end);
}
}
/**
* Class used to communicate execution time info to the StatsGenerator.
*
* @author rcypher
*/
static class ExecTimeInfo implements StatsGenerator.ProfileObj {
/** True if this is the start of execution. */
private boolean start;
/** Instruction count for this event. */
long instructionCount;
/** Maximum stack size for this event. */
private long maxStackSize;
/** The largest number of program nodes in simultaneous existence. */
private long maxSimultaneousNodes;
/** The total number of nodes created. */
private long nodeCount;
ExecTimeInfo (boolean start, long instructionCount) {
this.start = start;
this.instructionCount = instructionCount;
}
/**
* If the given StatsObject corresponds to this instance
* update it.
* @param stats
* @return the updated StatsObject, null if it didn't match.
*/
public StatsObject updateStats (StatsObject stats) {
if (stats == null) {
// Simply create a new one to hold the info
stats = new ExecTimeStats ();
}
if (!(stats instanceof ExecTimeStats)) {
// Didn't match return null.
return null;
}
//Update the info in the StatsObject with the information in
// this instance.
ExecTimeStats mStats = (ExecTimeStats)stats;
if (mStats.maxStackSize < maxStackSize) {
mStats.maxStackSize = maxStackSize;
}
if (mStats.maxSimultaneousNodes < maxSimultaneousNodes) {
mStats.maxSimultaneousNodes = maxSimultaneousNodes;
}
if (start) {
mStats.executionStart = System.currentTimeMillis();
mStats.executionStartInstructionCount = instructionCount;
mStats.execStarted = true;
} else {
if (!mStats.execStarted) {
if (mStats.execTimes.isEmpty()) {
return mStats;
}
mStats.execTimes.remove(mStats.execTimes.size() - 1);
mStats.execCounts.remove(mStats.execCounts.size() - 1);
}
mStats.execTimes.add (new Long (System.currentTimeMillis() - mStats.executionStart));
mStats.execCounts.add (new Long (instructionCount - mStats.executionStartInstructionCount));
mStats.execStarted = false;
}
if (mStats.executionNodes == -1) {
mStats.executionNodesStart = nodeCount;
}
mStats.executionNodes = nodeCount - mStats.executionNodesStart;
return stats;
}
/**
* Class used by the StatsGenerator to accumulate execution performance information.
*
* @author rcypher
*/
class ExecTimeStats extends StatsGenerator.PerformanceStatsObject {
/** The total execution time in ms. */
long executionTime = -1;
/** The time at which execution started, in ms. */
long executionStart = 0;
/** The number of instructions in the execution. */
long executionInstructionCount = -1;
/** The number of instructions at the beginning of execution. */
long executionStartInstructionCount = 0;
/** Vector to hold individual contributions to the time. */
final List<Long> execTimes = new ArrayList<Long> ();
/** Vector to hold individual contributions to the instruction count. */
final List<Long> execCounts = new ArrayList<Long> ();
// These members are only set if space use diagnostics are enabled in
// the executor.
/** Maximum stack size while executing. */
long maxStackSize = 0;
/** Maximum number of simultaneously existing program nodes. */
long maxSimultaneousNodes = 0;
/** Number of nodes allocated while executing. */
long executionNodes = -1;
/** The number of nodes already allocated at the start of exection. */
long executionNodesStart = 0;
/** True if execution has started. */
boolean execStarted = false;
/**
* Generate a short message describing the information in this object.
* @return the description
*/
public String generateShortMessage() {
DecimalFormat numFormat = new DecimalFormat("###,###.###");
StringBuilder sb = new StringBuilder ();
if (Executor.RUNTIME_STATS) {
sb.append (" " + numFormat.format(getRunInstructionCount()) + " instructions in " + numFormat.format(getRunTime()) + "ms, fips = " + numFormat.format(getRunFIPS()));
} else {
sb.append ("Time = " + numFormat.format(getRunTime()) + "ms.");
}
return sb.toString ();
}
/**
* Generate a long description of the information in this object.
* @return the description
*/
public String generateLongMessage() {
DecimalFormat numFormat = new DecimalFormat("###,###.###");
StringBuilder sb = new StringBuilder ();
if (Executor.RUNTIME_STATS) {
sb.append ("\n" + numFormat.format(getRunInstructionCount()) + " instructions in " + numFormat.format(getRunTime()) + "ms, fips = " + numFormat.format(getRunFIPS()));
sb.append ("\n");
} else {
sb.append ("Time = " + numFormat.format(getRunTime()) + "ms.");
}
return sb.toString();
}
/**
* Summarize a list of ExecTimeStats into a single SummarizedExecTimes object.
* @param v
* @return the summarized info
* {@inheritDoc}
*/
public StatsObject summarize (List<StatsObject> v) {
// If there are more than 3 runs we will discard the first run
// before calculating average, standard deviation, etc.
int nRuns = v.size();
int nstart = (nRuns >= 3) ? 1 : 0;
int nend = nRuns;
int ncount = nend - nstart;
long allExecTimes[] = new long [nRuns];
long allExecFips[] = new long [nRuns];
{
int i = 0;
for (final StatsObject statsObject : v) {
ExecTimeStats stats = (ExecTimeStats) statsObject;
allExecTimes [i] = stats.getRunTime();
allExecFips [i] = stats.getRunFIPS();
i++;
}
}
long firstExecTime = allExecTimes [0];
long firstExecFips = allExecFips [0];
SummarizedExecTimes ptr = new SummarizedExecTimes ();
ptr.allExecTimes = new long[allExecTimes.length];
System.arraycopy(allExecTimes, 0, ptr.allExecTimes, 0, allExecTimes.length);
long avgExecTime = 0;
long avgExecFips = 0;
for (int i = nstart; i < nend; ++i) {
avgExecTime += allExecTimes [i];
avgExecFips += allExecFips [i];
}
avgExecTime /= (ncount);
avgExecFips /= (ncount);
// calculate standard deviation.
double stDev = 0.0;
long avg = 0;
for (int i = nstart; i < nend; ++i) {
avg += allExecTimes [i];
}
avg /= ncount;
long summ = 0;
for (int i = nstart; i < nend; ++i) {
long diff = allExecTimes [i] - avg;
summ += (diff * diff);
}
if (ncount > 1) {
stDev = ((double)summ) / ((double)(ncount-1));
stDev = Math.sqrt(stDev);
} else {
stDev = 0.0;
}
stDev = ((int)(stDev * 100.0)) / 100.0;
double stdDevPercent = (stDev / avgExecTime) * 100.0;
stdDevPercent = ((int)(stdDevPercent * 100.0)) / 100.0;
//calculate the standard error:
double standardError = stDev / Math.sqrt(ncount);
ptr.frRunTime = firstExecTime;
ptr.frRunFips = firstExecFips;
ptr.avgRunTime = avgExecTime;
ptr.avgRunFips = avgExecFips;
ptr.runInstructions = ((ExecTimeStats)v.get(0)).getRunInstructionCount();
ptr.stdDev = stDev;
ptr.stdDevPercent = stdDevPercent;
ptr.standardError = standardError;
return ptr;
}
/**
* Return the execution time (i.e. time spent running before reaching initial WHNF).
* @return the run time
*/
public long getRunTime () {
if (executionTime == -1) {
executionTime = 0;
for (final Long l : execTimes) {
executionTime += l.longValue();
}
}
return executionTime;
}
/**
* Return the number of instructions executed while running.
* @return the instruction count
*/
public long getRunInstructionCount () {
if (executionInstructionCount == -1) {
executionInstructionCount = 0;
for (final Long l : execCounts) {
executionInstructionCount += l.longValue();
}
}
return executionInstructionCount;
}
/**
* @return long the run fips (functional instructions per second) value.
*/
public long getRunFIPS () {
return (getRunTime() == 0) ? -1 : (getRunInstructionCount() * 1000) / getRunTime();
}
/**
* @return the run time
*/
@Override
public long getAverageRunTime() {
return getRunTime();
}
@Override
public long getMedianRunTime() {
return getRunTime();
}
@Override
public double getStdDeviation() {
return 0;
}
@Override
public int getNRuns() {
return 1;
}
@Override
public long[] getRunTimes() {
return new long[]{getRunTime()};
}
}
/**
* Class used to hold the summarization of a set of run times.
*
* @author rcypher
*/
static class SummarizedExecTimes extends StatsGenerator.PerformanceStatsObject {
/** Time of first run. */
long frRunTime;
/** Instructions per second of first run. */
long frRunFips;
/** Average run time. */
long avgRunTime;
/** Average instructions per second. */
long avgRunFips;
/** Number of instructions in a single run. */
long runInstructions;
/** Standard deviation of the run times. */
double stdDev;
/** Standard deviation of the run times as a percentage of the average run time. */
double stdDevPercent;
/** Standard error of the mean. */
double standardError;
/** The run times for all runs. */
long allExecTimes[];
/**
* Generate a short message describing the summarized run times.
* @return the description.
*/
public String generateShortMessage() {
DecimalFormat numFormat = new DecimalFormat("###,###.###");
StringBuilder sb = new StringBuilder ();
if (Executor.RUNTIME_STATS) {
String s = makeDisplayLine ("Average time = ", avgRunTime, runInstructions, avgRunFips);
sb.append (s + "\n");
sb.append ("standard deviation of runs = " + numFormat.format(stdDev) + "ms or " + numFormat.format(stdDevPercent) + "% of average\n");
} else {
sb.append ("Average time = " + numFormat.format(avgRunTime) + " ms.");
}
return sb.toString();
}
/**
* Generate a long message describing the summarized run times.
*
* @return the description.
*/
public String generateLongMessage() {
DecimalFormat numFormat = new DecimalFormat("###,###.###");
StringBuilder sb = new StringBuilder ();
if (allExecTimes.length >= 3) {
sb.append("Individual runs: (first run discarded)\n");
for (int i = 0; i < allExecTimes.length; ++i) {
sb.append ("run " + i + ":\t" + numFormat.format(allExecTimes[i]));
if (i == 0) {
sb.append(" (discarded)\n");
} else {
sb.append('\n');
}
}
} else {
sb.append("Individual runs: \n");
for (int i = 0; i < allExecTimes.length; ++i) {
sb.append ("run " + i + ":\t" + numFormat.format(allExecTimes[i]) + "\n");
}
}
sb.append("First Run:\n");
String s = makeDisplayLine ("Execution Time = ", frRunTime, runInstructions, frRunFips);
sb.append(s + "\n");
sb.append("Summary:\n");
s = makeDisplayLine ("Average time = ", avgRunTime, runInstructions, avgRunFips);
sb.append(s + "\n");
sb.append("Standard deviation of runs = " + numFormat.format(stdDev) + "ms or " + numFormat.format(stdDevPercent) + "% of average\n");
sb.append("Standard error of mean = " + numFormat.format(standardError) + "ms or " + numFormat.format(standardError/avgRunTime*100) + "% of average\n");
sb.append("Minimum run time time = " + numFormat.format(getMinTime()) + "ms\n");
return sb.toString();
}
public long getRunTime () {
return avgRunTime;
}
/** {@inheritDoc} */
public StatsObject summarize (List<StatsObject> runs) {
// There is no way to summarize summarized objects.
return null;
}
private String makeDisplayLine (String start, long time, long instructions, long fips) {
DecimalFormat numFormat = new DecimalFormat("###,###.###");
StringBuilder sb = new StringBuilder ();
sb.append (start);
sb.append (padString (6, numFormat.format(time)));
sb.append ("ms Instructions = ");
sb.append (padString (6, numFormat.format(instructions)));
sb.append (" Fips = ");
sb.append (padString (6, numFormat.format(fips)));
return sb.toString ();
}
private String padString (int n, String s) {
StringBuilder sb = new StringBuilder ();
for (int i = 0; i < (n - s.length ()); ++i) {
sb.append (" ");
}
sb.append (s);
return sb.toString ();
}
@Override
public long getAverageRunTime() {
return avgRunTime;
}
@Override
public long getMedianRunTime() {
if (allExecTimes.length == 1) {
return allExecTimes[0];
}
long times[] = new long[allExecTimes.length];
System.arraycopy(allExecTimes, 0, times, 0, times.length);
Arrays.sort(times);
if ((times.length % 2) == 0) {
return (times[times.length/2] + times[(times.length/2)-1]) / 2;
}
return times[(times.length/2)];
}
@Override
public double getStdDeviation() {
return stdDev;
}
/**
* @return the standard error of the mean
*/
public double getStandardError() {
return standardError;
}
/**
* @return the number of runs used to calculate the average run time and standard deviation
*/
@Override
public int getNRuns() {
return allExecTimes.length >= 3 ? allExecTimes.length - 1 : allExecTimes.length;
}
/**
* Return the times for all runs in the order the runs occurred.
* Note: there may be more values than indicated by getNRuns()
* because the fastest/slowest times are discarded when calculating
* average time and standard deviations.
* @return the individual run times.
*/
@Override
public long[] getRunTimes() {
long times[] = new long[allExecTimes.length];
System.arraycopy(allExecTimes, 0, times, 0, times.length);
return times;
}
}
}
/**
* A ProfileObj used to communicate information about the number of calls
* to various CAL entities to the StatsGenerator.
*
* @author rcypher
*/
static class CallCountInfo implements StatsGenerator.ProfileObj {
/** Qualified name of the CAL entity which has been called. */
private QualifiedName name;
/** A description of the type of counts being recorded. e.g. supercombinator, data constructor, etc. */
private String type;
CallCountInfo (QualifiedName name, String type) {
this.name = name;
this.type = type;
}
/**
* @return the qualified name of the entity we are counting
*/
QualifiedName getName() {
return name;
}
/**
* @return the description of the type of count. e.g. supercombinator, data constructor, etc.
*/
String getType() {
return type;
}
/**
* If the given StatsObject is of the corresponding type
* update it with the information in this object.
* @param stats - the StatsObject to update.
* @return the updated StatsObject, null if it didn't match this object.
*/
public StatsObject updateStats (StatsObject stats) {
/*
* If the StatsObject is null we simply create a new one
* which will hold the information.
*/
if (stats == null) {
stats = new CallCountStats (type);
}
// If the StatsObject is the wrong type return null
if (!(stats instanceof CallCountStats)) {
return null;
}
// Update the StatsObject with this count.
CallCountStats ccStats = (CallCountStats)stats;
if (!ccStats.type.equals(type)) {
return null;
}
Map<QualifiedName, CallCount> counts = ccStats.getCounts();
CallCount count = counts.get (name);
if (count == null) {
count = new CallCount (name, 1);
counts.put (name, count);
} else {
count.count += 1;
}
return stats;
}
/**
* A StatsObject class used to accumulate call count information.
*
* @author rcypher
*/
static class CallCountStats implements StatsGenerator.StatsObject {
private Map<QualifiedName, CallCount> counts = new HashMap<QualifiedName, CallCount>();
/** A description of the type of counts being accumulated. e.g. supercombinator, data constructor, etc. */
String type;
public CallCountStats (String type) {
this.type = type;
}
public Map<QualifiedName, CallCount> getCounts () {
return counts;
}
/**
* Generate a short message describing the information in this object.
* @return string containing the description.
*/
public String generateShortMessage() {
// There is no short form simply get the long form.
return generateLongMessage();
}
/**
* Generate the long description of the information in this object.
* @return the long description of this object
*/
public String generateLongMessage() {
// Sort the CallCount instances by frequency.
List<CallCount> sortedCounts = new ArrayList<CallCount> ();
for (final Map.Entry<QualifiedName, CallCount> entry : counts.entrySet()) {
sortedCounts.add (entry.getValue());
}
Collections.sort (sortedCounts);
StringBuilder sb = new StringBuilder();
sb.append(type + ":\n");
long totalCounts = 0;
for (final CallCount count : sortedCounts) {
sb.append (count.count + "\t\t-> " + count.name + "\n");
totalCounts += count.count;
}
sb.append (" total counts = " + totalCounts + "\n\n");
return sb.toString();
}
/** {@inheritDoc} */
public StatsObject summarize(List<StatsObject> runs) {
return this;
}
}
/**
* A utility class used to hold the count for a named function.
*
* @author rcypher
*/
static class CallCount implements Comparable<CallCount> {
/** The name of the CAL entity that is being counted. */
final QualifiedName name;
/** The number of times the entity is invoked, referenced, etc. */
int count;
CallCount (QualifiedName name, int count) {
this.name = name;
this.count = count;
}
/**
* Allow for sorting by number of calls.
* @param cc
* @return 0 if equal, > 0 if greater, < 0 if less.
*/
public int compareTo (CallCount cc) {
if (cc == null) {
return 1;
}
int compareNames = name.compareTo(cc.name);
if (compareNames != 0) {
return compareNames;
}
if (cc.count > count) {
return 1;
} else if (cc.count == count) {
return 0;
} else {
return -1;
}
}
@Override
public boolean equals (Object o) {
if (o == null || !(o instanceof CallCount)) {
return false;
}
CallCount cc = (CallCount)o;
return name.equals(cc.name) && count == cc.count;
}
@Override
public int hashCode () {
return name.hashCode() + count;
}
}
}
/**
* Since for the g-machine the execution context has not state and functionality
* other than being an identifier to associate CAF instances it is implemented as an
* empty class.
*/
static class GExecutionContext extends ExecutionContextImpl {
GExecutionContext (ExecutionContextProperties properties) {
super (properties);
}
void reset (GProgram program, ResourceAccess resourceAccess) {
assert (program != null) : "Invalid Program reference in GExecutionContext";
setRuntimeEnvironment(new DynamicRuntimeEnvironment(program, resourceAccess));
}
/**
* Null out the program field.
* This prevents the execution context from holding on to
* an out-of-date instance of Program (as held by the DynamicRuntimeEnvironment.
*/
void clearProgram () {
setRuntimeEnvironment (null);
}
}
GExecutionContext getExecutionContext() {
return (GExecutionContext)executionContext;
}
}