blob: 4c0e0a3a7c6c9108806b1bc8e4baf782c3819b4c [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
import java.util.Arrays;
import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.lookup.MethodHandleFactory;
import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
import jdk.nashorn.internal.objects.Global;
import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
/**
* Used to signal to the linker to relink the callee
*/
@SuppressWarnings("serial")
public final class RewriteException extends Exception {
private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
// Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
// optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
private ScriptObject runtimeScope;
// Contents of bytecode slots
private Object[] byteCodeSlots;
private final int[] previousContinuationEntryPoints;
/** Call for getting the contents of the bytecode slots in the exception */
public static final Call GET_BYTECODE_SLOTS = virtualCallNoLookup(RewriteException.class, "getByteCodeSlots", Object[].class);
/** Call for getting the program point in the exception */
public static final Call GET_PROGRAM_POINT = virtualCallNoLookup(RewriteException.class, "getProgramPoint", int.class);
/** Call for getting the return value for the exception */
public static final Call GET_RETURN_VALUE = virtualCallNoLookup(RewriteException.class, "getReturnValueDestructive", Object.class);
/** Call for the populate array bootstrap */
public static final Call BOOTSTRAP = staticCallNoLookup(RewriteException.class, "populateArrayBootstrap", CallSite.class, Lookup.class, String.class, MethodType.class, int.class);
/** Call for populating an array with local variable state */
private static final Call POPULATE_ARRAY = staticCall(MethodHandles.lookup(), RewriteException.class, "populateArray", Object[].class, Object[].class, int.class, Object[].class);
/** Call for converting an array to a long array. */
public static final Call TO_LONG_ARRAY = staticCallNoLookup(RewriteException.class, "toLongArray", long[].class, Object.class, RewriteException.class);
/** Call for converting an array to a double array. */
public static final Call TO_DOUBLE_ARRAY = staticCallNoLookup(RewriteException.class, "toDoubleArray", double[].class, Object.class, RewriteException.class);
/** Call for converting an array to an object array. */
public static final Call TO_OBJECT_ARRAY = staticCallNoLookup(RewriteException.class, "toObjectArray", Object[].class, Object.class, RewriteException.class);
/** Call for converting an object to null if it can't be represented as an instance of a class. */
public static final Call INSTANCE_OR_NULL = staticCallNoLookup(RewriteException.class, "instanceOrNull", Object.class, Object.class, Class.class);
/** Call for asserting the length of an array. */
public static final Call ASSERT_ARRAY_LENGTH = staticCallNoLookup(RewriteException.class, "assertArrayLength", void.class, Object[].class, int.class);
private RewriteException(
final UnwarrantedOptimismException e,
final Object[] byteCodeSlots,
final String[] byteCodeSymbolNames,
final int[] previousContinuationEntryPoints) {
super("", e, false, Context.DEBUG);
this.byteCodeSlots = byteCodeSlots;
this.runtimeScope = mergeSlotsWithScope(byteCodeSlots, byteCodeSymbolNames);
this.previousContinuationEntryPoints = previousContinuationEntryPoints;
}
/**
* Constructor for a rewrite exception thrown from an optimistic function.
* @param e the {@link UnwarrantedOptimismException} that triggered this exception.
* @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
* @param byteCodeSymbolNames the names of the variables in the {@code byteCodeSlots} parameter. The array might
* have less elements, and some elements might be unnamed (the name can be null). The information is provided in an
* effort to assist evaluation of expressions for their types by the compiler doing the deoptimizing recompilation,
* and can thus be incomplete - the more complete it is, the more expressions can be evaluated by the compiler, and
* the more unnecessary deoptimizing compilations can be avoided.
* @return a new rewrite exception
*/
public static RewriteException create(final UnwarrantedOptimismException e,
final Object[] byteCodeSlots,
final String[] byteCodeSymbolNames) {
return create(e, byteCodeSlots, byteCodeSymbolNames, null);
}
/**
* Constructor for a rewrite exception thrown from a rest-of method.
* @param e the {@link UnwarrantedOptimismException} that triggered this exception.
* @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
* @param byteCodeSymbolNames the names of the variables in the {@code byteCodeSlots} parameter. The array might
* have less elements, and some elements might be unnamed (the name can be null). The information is provided in an
* effort to assist evaluation of expressions for their types by the compiler doing the deoptimizing recompilation,
* and can thus be incomplete - the more complete it is, the more expressions can be evaluated by the compiler, and
* the more unnecessary deoptimizing compilations can be avoided.
* @param previousContinuationEntryPoints an array of continuation entry points that were already executed during
* one logical invocation of the function (a rest-of triggering a rest-of triggering a...)
* @return a new rewrite exception
*/
public static RewriteException create(final UnwarrantedOptimismException e,
final Object[] byteCodeSlots,
final String[] byteCodeSymbolNames,
final int[] previousContinuationEntryPoints) {
return new RewriteException(e, byteCodeSlots, byteCodeSymbolNames, previousContinuationEntryPoints);
}
/**
* Bootstrap method for populate array
* @param lookup lookup
* @param name name (ignored)
* @param type method type for signature
* @param startIndex start index to start writing to
* @return callsite to array populator (constant)
*/
public static CallSite populateArrayBootstrap(final MethodHandles.Lookup lookup, final String name, final MethodType type, final int startIndex) {
MethodHandle mh = POPULATE_ARRAY.methodHandle();
mh = MH.insertArguments(mh, 1, startIndex);
mh = MH.asCollector(mh, Object[].class, type.parameterCount() - 1);
mh = MH.asType(mh, type);
return new ConstantCallSite(mh);
}
private static ScriptObject mergeSlotsWithScope(final Object[] byteCodeSlots, final String[] byteCodeSymbolNames) {
final ScriptObject locals = Global.newEmptyInstance();
final int l = Math.min(byteCodeSlots.length, byteCodeSymbolNames.length);
ScriptObject runtimeScope = null;
final String scopeName = CompilerConstants.SCOPE.symbolName();
for(int i = 0; i < l; ++i) {
final String name = byteCodeSymbolNames[i];
final Object value = byteCodeSlots[i];
if(scopeName.equals(name)) {
assert runtimeScope == null;
runtimeScope = (ScriptObject)value;
} else if(name != null) {
locals.set(name, value, NashornCallSiteDescriptor.CALLSITE_STRICT);
}
}
locals.setProto(runtimeScope);
return locals;
}
/**
* Array populator used for saving the local variable state into the array contained in the
* RewriteException
* @param arrayToBePopluated array to be populated
* @param startIndex start index to write to
* @param items items with which to populate the array
* @return the populated array - same array object
*/
public static Object[] populateArray(final Object[] arrayToBePopluated, final int startIndex, final Object[] items) {
System.arraycopy(items, 0, arrayToBePopluated, startIndex, items.length);
return arrayToBePopluated;
}
/**
* Continuation handler calls this method when a local variable carried over into the continuation is expected to be
* a long array in the continued method. Normally, it will also be a long array in the original (interrupted by
* deoptimization) method, but it can actually be an int array that underwent widening in the new code version.
* @param obj the object that has to be converted into a long array
* @param e the exception being processed
* @return a long array
*/
public static long[] toLongArray(final Object obj, final RewriteException e) {
if(obj instanceof long[]) {
return (long[])obj;
}
assert obj instanceof int[];
final int[] in = (int[])obj;
final long[] out = new long[in.length];
for(int i = 0; i < in.length; ++i) {
out[i] = in[i];
}
return e.replaceByteCodeValue(in, out);
}
/**
* Continuation handler calls this method when a local variable carried over into the continuation is expected to be
* a double array in the continued method. Normally, it will also be a double array in the original (interrupted by
* deoptimization) method, but it can actually be an int or long array that underwent widening in the new code version.
* @param obj the object that has to be converted into a double array
* @param e the exception being processed
* @return a double array
*/
public static double[] toDoubleArray(final Object obj, final RewriteException e) {
if(obj instanceof double[]) {
return (double[])obj;
}
assert obj instanceof int[] || obj instanceof long[];
final int l = Array.getLength(obj);
final double[] out = new double[l];
for(int i = 0; i < l; ++i) {
out[i] = Array.getDouble(obj, i);
}
return e.replaceByteCodeValue(obj, out);
}
/**
* Continuation handler calls this method when a local variable carried over into the continuation is expected to be
* an Object array in the continued method. Normally, it will also be an Object array in the original (interrupted by
* deoptimization) method, but it can actually be an int, long, or double array that underwent widening in the new
* code version.
* @param obj the object that has to be converted into an Object array
* @param e the exception being processed
* @return an Object array
*/
public static Object[] toObjectArray(final Object obj, final RewriteException e) {
if(obj instanceof Object[]) {
return (Object[])obj;
}
assert obj instanceof int[] || obj instanceof long[] || obj instanceof double[] : obj + " is " + obj.getClass().getName();
final int l = Array.getLength(obj);
final Object[] out = new Object[l];
for(int i = 0; i < l; ++i) {
out[i] = Array.get(obj, i);
}
return e.replaceByteCodeValue(obj, out);
}
/**
* Continuation handler calls this method when a local variable carried over into the continuation is expected to
* have a certain type, but the value can have a different type coming from the deoptimized method as it was a dead
* store. If we had precise liveness analysis, we wouldn't need this.
* @param obj the object inspected for being of a particular type
* @param clazz the type the object must belong to
* @return the object if it belongs to the type, or null otherwise
*/
public static Object instanceOrNull(final Object obj, final Class<?> clazz) {
return clazz.isInstance(obj) ? obj : null;
}
/**
* Asserts the length of an array. Invoked from continuation handler only when running with assertions enabled.
* The array can, in fact, have more elements than asserted, but they must all have Undefined as their value. The
* method does not test for the array having less elements than asserted, as those would already have caused an
* {@code ArrayIndexOutOfBoundsException} to be thrown as the continuation handler attempts to access the missing
* elements.
* @param arr the array
* @param length the asserted length
*/
public static void assertArrayLength(final Object[] arr, final int length) {
for(int i = arr.length; i-- > length;) {
if(arr[i] != ScriptRuntime.UNDEFINED) {
throw new AssertionError(String.format("Expected array length %d, but it is %d", length, i + 1));
}
}
}
private <T> T replaceByteCodeValue(final Object in, final T out) {
for(int i = 0; i < byteCodeSlots.length; ++i) {
if(byteCodeSlots[i] == in) {
byteCodeSlots[i] = out;
}
}
return out;
}
private UnwarrantedOptimismException getUOE() {
return (UnwarrantedOptimismException)getCause();
}
/**
* Get return value. This method is destructive, after it is invoked subsequent invocation of either
* {@link #getByteCodeSlots()} or this method will return null. This method is invoked from the generated
* continuation code as the last step before continuing the execution, and we need to make sure we don't hang on to
* either the entry bytecode slot values or the return value and prevent them from being garbage collected.
* @return return value
*/
public Object getReturnValueDestructive() {
assert byteCodeSlots != null;
byteCodeSlots = null;
runtimeScope = null;
return getUOE().getReturnValueDestructive();
}
Object getReturnValueNonDestructive() {
return getUOE().getReturnValueNonDestructive();
}
/**
* Get return type
* @return return type
*/
public Type getReturnType() {
return getUOE().getReturnType();
}
/**
* Get the program point.
* @return program point.
*/
public int getProgramPoint() {
return getUOE().getProgramPoint();
}
/**
* Get the bytecode slot contents.
* @return bytecode slot contents.
*/
public Object[] getByteCodeSlots() {
return byteCodeSlots == null ? null : byteCodeSlots.clone();
}
/**
* @return an array of continuation entry points that were already executed during one logical invocation of the
* function (a rest-of triggering a rest-of triggering a...)
*/
public int[] getPreviousContinuationEntryPoints() {
return previousContinuationEntryPoints == null ? null : previousContinuationEntryPoints.clone();
}
/**
* Returns the runtime scope that was in effect when the exception was thrown.
* @return the runtime scope.
*/
public ScriptObject getRuntimeScope() {
return runtimeScope;
}
private static String stringify(final Object returnValue) {
if (returnValue == null) {
return "null";
}
String str = returnValue.toString();
if (returnValue instanceof String) {
str = '\'' + str + '\'';
} else if (returnValue instanceof Double) {
str = str + 'd';
} else if (returnValue instanceof Long) {
str = str + 'l';
}
return str;
}
@Override
public String getMessage() {
return getMessage(false);
}
/**
* Short toString function for message
* @return short message
*/
public String getMessageShort() {
return getMessage(true);
}
private String getMessage(final boolean isShort) {
final StringBuilder sb = new StringBuilder();
//program point
sb.append("[pp=").
append(getProgramPoint()).
append(", ");
//slot contents
if (!isShort) {
final Object[] slots = byteCodeSlots;
if (slots != null) {
sb.append("slots=").
append(Arrays.asList(slots)).
append(", ");
}
}
//return type
sb.append("type=").
append(getReturnType()).
append(", ");
//return value
sb.append("value=").
append(stringify(getReturnValueNonDestructive())).
append(")]");
return sb.toString();
}
private void writeObject(final ObjectOutputStream out) throws NotSerializableException {
throw new NotSerializableException(getClass().getName());
}
private void readObject(final ObjectInputStream in) throws NotSerializableException {
throw new NotSerializableException(getClass().getName());
}
}