/* * 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 += 'd'; } else if (returnValue instanceof Long) { 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()); } }