RewriteException.java revision 1033:c1f651636d9c
1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package jdk.nashorn.internal.runtime;
26
27import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
28import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
29import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
30
31import java.io.NotSerializableException;
32import java.io.ObjectInputStream;
33import java.io.ObjectOutputStream;
34import java.lang.invoke.CallSite;
35import java.lang.invoke.ConstantCallSite;
36import java.lang.invoke.MethodHandle;
37import java.lang.invoke.MethodHandles;
38import java.lang.invoke.MethodHandles.Lookup;
39import java.lang.invoke.MethodType;
40import java.lang.reflect.Array;
41import java.util.Arrays;
42import jdk.nashorn.internal.codegen.CompilerConstants;
43import jdk.nashorn.internal.codegen.CompilerConstants.Call;
44import jdk.nashorn.internal.codegen.types.Type;
45import jdk.nashorn.internal.lookup.MethodHandleFactory;
46import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
47import jdk.nashorn.internal.objects.Global;
48import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
49
50/**
51 * Used to signal to the linker to relink the callee
52 */
53@SuppressWarnings("serial")
54public final class RewriteException extends Exception {
55    private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
56
57    // Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
58    // optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
59    private ScriptObject runtimeScope;
60
61    // Contents of bytecode slots
62    private Object[] byteCodeSlots;
63
64    private final int[] previousContinuationEntryPoints;
65
66    /** Call for getting the contents of the bytecode slots in the exception */
67    public static final Call GET_BYTECODE_SLOTS       = virtualCallNoLookup(RewriteException.class, "getByteCodeSlots", Object[].class);
68    /** Call for getting the program point in the exception */
69    public static final Call GET_PROGRAM_POINT        = virtualCallNoLookup(RewriteException.class, "getProgramPoint", int.class);
70    /** Call for getting the return value for the exception */
71    public static final Call GET_RETURN_VALUE         = virtualCallNoLookup(RewriteException.class, "getReturnValueDestructive", Object.class);
72    /** Call for the populate array bootstrap */
73    public static final Call BOOTSTRAP                = staticCallNoLookup(RewriteException.class, "populateArrayBootstrap", CallSite.class, Lookup.class, String.class, MethodType.class, int.class);
74
75    /** Call for populating an array with local variable state */
76    private static final Call POPULATE_ARRAY           = staticCall(MethodHandles.lookup(), RewriteException.class, "populateArray", Object[].class, Object[].class, int.class, Object[].class);
77
78    /** Call for converting an array to a long array. */
79    public static final Call TO_LONG_ARRAY   = staticCallNoLookup(RewriteException.class, "toLongArray",   long[].class, Object.class, RewriteException.class);
80    /** Call for converting an array to a double array. */
81    public static final Call TO_DOUBLE_ARRAY = staticCallNoLookup(RewriteException.class, "toDoubleArray", double[].class, Object.class, RewriteException.class);
82    /** Call for converting an array to an object array. */
83    public static final Call TO_OBJECT_ARRAY = staticCallNoLookup(RewriteException.class, "toObjectArray", Object[].class, Object.class, RewriteException.class);
84    /** Call for converting an object to null if it can't be represented as an instance of a class. */
85    public static final Call INSTANCE_OR_NULL = staticCallNoLookup(RewriteException.class, "instanceOrNull", Object.class, Object.class, Class.class);
86    /** Call for asserting the length of an array. */
87    public static final Call ASSERT_ARRAY_LENGTH = staticCallNoLookup(RewriteException.class, "assertArrayLength", void.class, Object[].class, int.class);
88
89    private RewriteException(
90            final UnwarrantedOptimismException e,
91            final Object[] byteCodeSlots,
92            final String[] byteCodeSymbolNames,
93            final int[] previousContinuationEntryPoints) {
94        super("", e, false, Context.DEBUG);
95        this.byteCodeSlots = byteCodeSlots;
96        this.runtimeScope = mergeSlotsWithScope(byteCodeSlots, byteCodeSymbolNames);
97        this.previousContinuationEntryPoints = previousContinuationEntryPoints;
98    }
99
100    /**
101     * Constructor for a rewrite exception thrown from an optimistic function.
102     * @param e the {@link UnwarrantedOptimismException} that triggered this exception.
103     * @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
104     * @param byteCodeSymbolNames the names of the variables in the {@code byteCodeSlots} parameter. The array might
105     * have less elements, and some elements might be unnamed (the name can be null). The information is provided in an
106     * effort to assist evaluation of expressions for their types by the compiler doing the deoptimizing recompilation,
107     * and can thus be incomplete - the more complete it is, the more expressions can be evaluated by the compiler, and
108     * the more unnecessary deoptimizing compilations can be avoided.
109     * @return a new rewrite exception
110     */
111    public static RewriteException create(final UnwarrantedOptimismException e,
112            final Object[] byteCodeSlots,
113            final String[] byteCodeSymbolNames) {
114        return create(e, byteCodeSlots, byteCodeSymbolNames, null);
115    }
116
117    /**
118     * Constructor for a rewrite exception thrown from a rest-of method.
119     * @param e the {@link UnwarrantedOptimismException} that triggered this exception.
120     * @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
121     * @param byteCodeSymbolNames the names of the variables in the {@code byteCodeSlots} parameter. The array might
122     * have less elements, and some elements might be unnamed (the name can be null). The information is provided in an
123     * effort to assist evaluation of expressions for their types by the compiler doing the deoptimizing recompilation,
124     * and can thus be incomplete - the more complete it is, the more expressions can be evaluated by the compiler, and
125     * the more unnecessary deoptimizing compilations can be avoided.
126     * @param previousContinuationEntryPoints an array of continuation entry points that were already executed during
127     * one logical invocation of the function (a rest-of triggering a rest-of triggering a...)
128     * @return a new rewrite exception
129     */
130    public static RewriteException create(final UnwarrantedOptimismException e,
131            final Object[] byteCodeSlots,
132            final String[] byteCodeSymbolNames,
133            final int[] previousContinuationEntryPoints) {
134        return new RewriteException(e, byteCodeSlots, byteCodeSymbolNames, previousContinuationEntryPoints);
135    }
136
137    /**
138     * Bootstrap method for populate array
139     * @param lookup     lookup
140     * @param name       name (ignored)
141     * @param type       method type for signature
142     * @param startIndex start index to start writing to
143     * @return callsite to array populator (constant)
144     */
145    public static CallSite populateArrayBootstrap(final MethodHandles.Lookup lookup, final String name, final MethodType type, final int startIndex) {
146        MethodHandle mh = POPULATE_ARRAY.methodHandle();
147        mh = MH.insertArguments(mh, 1, startIndex);
148        mh = MH.asCollector(mh, Object[].class, type.parameterCount() - 1);
149        mh = MH.asType(mh, type);
150        return new ConstantCallSite(mh);
151    }
152
153    private static ScriptObject mergeSlotsWithScope(final Object[] byteCodeSlots, final String[] byteCodeSymbolNames) {
154        final ScriptObject locals = Global.newEmptyInstance();
155        final int l = Math.min(byteCodeSlots.length, byteCodeSymbolNames.length);
156        ScriptObject runtimeScope = null;
157        final String scopeName = CompilerConstants.SCOPE.symbolName();
158        for(int i = 0; i < l; ++i) {
159            final String name = byteCodeSymbolNames[i];
160            final Object value = byteCodeSlots[i];
161            if(scopeName.equals(name)) {
162                assert runtimeScope == null;
163                runtimeScope = (ScriptObject)value;
164            } else if(name != null) {
165                locals.set(name, value, NashornCallSiteDescriptor.CALLSITE_STRICT);
166            }
167        }
168        locals.setProto(runtimeScope);
169        return locals;
170    }
171
172    /**
173     * Array populator used for saving the local variable state into the array contained in the
174     * RewriteException
175     * @param arrayToBePopluated array to be populated
176     * @param startIndex start index to write to
177     * @param items items with which to populate the array
178     * @return the populated array - same array object
179     */
180    public static Object[] populateArray(final Object[] arrayToBePopluated, final int startIndex, final Object[] items) {
181        System.arraycopy(items, 0, arrayToBePopluated, startIndex, items.length);
182        return arrayToBePopluated;
183    }
184
185    /**
186     * Continuation handler calls this method when a local variable carried over into the continuation is expected to be
187     * a long array in the continued method. Normally, it will also be a long array in the original (interrupted by
188     * deoptimization) method, but it can actually be an int array that underwent widening in the new code version.
189     * @param obj the object that has to be converted into a long array
190     * @param e the exception being processed
191     * @return a long array
192     */
193    public static long[] toLongArray(final Object obj, final RewriteException e) {
194        if(obj instanceof long[]) {
195            return (long[])obj;
196        }
197
198        assert obj instanceof int[];
199
200        final int[] in = (int[])obj;
201        final long[] out = new long[in.length];
202        for(int i = 0; i < in.length; ++i) {
203            out[i] = in[i];
204        }
205        return e.replaceByteCodeValue(in, out);
206    }
207
208    /**
209     * Continuation handler calls this method when a local variable carried over into the continuation is expected to be
210     * a double array in the continued method. Normally, it will also be a double array in the original (interrupted by
211     * deoptimization) method, but it can actually be an int or long array that underwent widening in the new code version.
212     * @param obj the object that has to be converted into a double array
213     * @param e the exception being processed
214     * @return a double array
215     */
216    public static double[] toDoubleArray(final Object obj, final RewriteException e) {
217        if(obj instanceof double[]) {
218            return (double[])obj;
219        }
220
221        assert obj instanceof int[] || obj instanceof long[];
222
223        final int l = Array.getLength(obj);
224        final double[] out = new double[l];
225        for(int i = 0; i < l; ++i) {
226            out[i] = Array.getDouble(obj, i);
227        }
228        return e.replaceByteCodeValue(obj, out);
229    }
230
231    /**
232     * Continuation handler calls this method when a local variable carried over into the continuation is expected to be
233     * an Object array in the continued method. Normally, it will also be an Object array in the original (interrupted by
234     * deoptimization) method, but it can actually be an int, long, or double array that underwent widening in the new
235     * code version.
236     * @param obj the object that has to be converted into an Object array
237     * @param e the exception being processed
238     * @return an Object array
239     */
240    public static Object[] toObjectArray(final Object obj, final RewriteException e) {
241        if(obj instanceof Object[]) {
242            return (Object[])obj;
243        }
244
245        assert obj instanceof int[] || obj instanceof long[] || obj instanceof double[] : obj + " is " + obj.getClass().getName();
246
247        final int l = Array.getLength(obj);
248        final Object[] out = new Object[l];
249        for(int i = 0; i < l; ++i) {
250            out[i] = Array.get(obj, i);
251        }
252        return e.replaceByteCodeValue(obj, out);
253    }
254
255    /**
256     * Continuation handler calls this method when a local variable carried over into the continuation is expected to
257     * have a certain type, but the value can have a different type coming from the deoptimized method as it was a dead
258     * store. If we had precise liveness analysis, we wouldn't need this.
259     * @param obj the object inspected for being of a particular type
260     * @param clazz the type the object must belong to
261     * @return the object if it belongs to the type, or null otherwise
262     */
263    public static Object instanceOrNull(final Object obj, final Class<?> clazz) {
264        return clazz.isInstance(obj) ? obj : null;
265    }
266
267    /**
268     * Asserts the length of an array. Invoked from continuation handler only when running with assertions enabled.
269     * The array can, in fact, have more elements than asserted, but they must all have Undefined as their value. The
270     * method does not test for the array having less elements than asserted, as those would already have caused an
271     * {@code ArrayIndexOutOfBoundsException} to be thrown as the continuation handler attempts to access the missing
272     * elements.
273     * @param arr the array
274     * @param length the asserted length
275     */
276    public static void assertArrayLength(final Object[] arr, final int length) {
277        for(int i = arr.length; i-- > length;) {
278            if(arr[i] != ScriptRuntime.UNDEFINED) {
279                throw new AssertionError(String.format("Expected array length %d, but it is %d", length, i + 1));
280            }
281        }
282    }
283
284    private <T> T replaceByteCodeValue(final Object in, final T out) {
285        for(int i = 0; i < byteCodeSlots.length; ++i) {
286            if(byteCodeSlots[i] == in) {
287                byteCodeSlots[i] = out;
288            }
289        }
290        return out;
291    }
292
293    private UnwarrantedOptimismException getUOE() {
294        return (UnwarrantedOptimismException)getCause();
295    }
296    /**
297     * Get return value. This method is destructive, after it is invoked subsequent invocation of either
298     * {@link #getByteCodeSlots()} or this method will return null. This method is invoked from the generated
299     * continuation code as the last step before continuing the execution, and we need to make sure we don't hang on to
300     * either the entry bytecode slot values or the return value and prevent them from being garbage collected.
301     * @return return value
302     */
303    public Object getReturnValueDestructive() {
304        assert byteCodeSlots != null;
305        byteCodeSlots = null;
306        runtimeScope = null;
307        return getUOE().getReturnValueDestructive();
308    }
309
310    Object getReturnValueNonDestructive() {
311        return getUOE().getReturnValueNonDestructive();
312    }
313
314    /**
315     * Get return type
316     * @return return type
317     */
318    public Type getReturnType() {
319        return getUOE().getReturnType();
320    }
321
322    /**
323     * Get the program point.
324     * @return program point.
325     */
326    public int getProgramPoint() {
327        return getUOE().getProgramPoint();
328    }
329
330    /**
331     * Get the bytecode slot contents.
332     * @return bytecode slot contents.
333     */
334    public Object[] getByteCodeSlots() {
335        return byteCodeSlots == null ? null : byteCodeSlots.clone();
336    }
337
338    /**
339     * @return an array of continuation entry points that were already executed during one logical invocation of the
340     * function (a rest-of triggering a rest-of triggering a...)
341     */
342    public int[] getPreviousContinuationEntryPoints() {
343        return previousContinuationEntryPoints == null ? null : previousContinuationEntryPoints.clone();
344    }
345
346    /**
347     * Returns the runtime scope that was in effect when the exception was thrown.
348     * @return the runtime scope.
349     */
350    public ScriptObject getRuntimeScope() {
351        return runtimeScope;
352    }
353
354    private static String stringify(final Object returnValue) {
355        if (returnValue == null) {
356            return "null";
357        }
358        String str = returnValue.toString();
359        if (returnValue instanceof String) {
360            str = '\'' + str + '\'';
361        } else if (returnValue instanceof Double) {
362            str = str + 'd';
363        } else if (returnValue instanceof Long) {
364            str = str + 'l';
365        }
366        return str;
367    }
368
369    @Override
370    public String getMessage() {
371        return getMessage(false);
372    }
373
374    /**
375     * Short toString function for message
376     * @return short message
377     */
378    public String getMessageShort() {
379        return getMessage(true);
380    }
381
382    private String getMessage(final boolean isShort) {
383        final StringBuilder sb = new StringBuilder();
384
385        //program point
386        sb.append("[pp=").
387            append(getProgramPoint()).
388            append(", ");
389
390        //slot contents
391        if (!isShort) {
392            final Object[] slots = byteCodeSlots;
393            if (slots != null) {
394                sb.append("slots=").
395                    append(Arrays.asList(slots)).
396                    append(", ");
397            }
398        }
399
400        //return type
401        sb.append("type=").
402            append(getReturnType()).
403            append(", ");
404
405        //return value
406        sb.append("value=").
407            append(stringify(getReturnValueNonDestructive())).
408            append(")]");
409
410        return sb.toString();
411    }
412
413    private void writeObject(final ObjectOutputStream out) throws NotSerializableException {
414        throw new NotSerializableException(getClass().getName());
415    }
416
417    private void readObject(final ObjectInputStream in) throws NotSerializableException {
418        throw new NotSerializableException(getClass().getName());
419    }
420}
421