diff --git a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java index f775050541e..43074a2c735 100644 --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -346,28 +346,30 @@ final class CodeGenerator extends NodeOperatorVisitor SharedScopeCall.FAST_SCOPE_GET_THRESHOLD && !identNode.isOptimistic()) { - // As shared scope vars are only used with non-optimistic identifiers, we switch from using TypeBounds to - // just a single definitive type, resultBounds.widest. - new OptimisticOperation(identNode, TypeBounds.OBJECT) { - @Override - void loadStack() { - method.loadCompilerConstant(SCOPE); - } - - @Override - void consumeStack() { - loadSharedScopeVar(resultBounds.widest, symbol, flags); - } - }.emit(); - } else { - new LoadFastScopeVar(identNode, resultBounds, flags).emit(); - } - } else { - //slow scope load, we have no proto depth + if (!isFastScope(symbol)) { + // slow scope load, prototype chain must be inspected at runtime new LoadScopeVar(identNode, resultBounds, flags).emit(); + } else if (identNode.isCompileTimePropertyName() || symbol.getUseCount() < SharedScopeCall.SHARED_GET_THRESHOLD) { + // fast scope load with known prototype depth + new LoadFastScopeVar(identNode, resultBounds, flags).emit(); + } else { + // Only generate shared scope getter for often used fast-scope symbols. + new OptimisticOperation(identNode, resultBounds) { + @Override + void loadStack() { + method.loadCompilerConstant(SCOPE); + final int depth = getScopeProtoDepth(lc.getCurrentBlock(), symbol); + assert depth >= 0; + method.load(depth); + method.load(getProgramPoint()); + } + + @Override + void consumeStack() { + final Type resultType = isOptimistic ? getOptimisticCoercedType() : resultBounds.widest; + lc.getScopeGet(unit, symbol, resultType, flags, isOptimistic).generateInvoke(method); + } + }.emit(); } return method; @@ -467,12 +469,6 @@ final class CodeGenerator extends NodeOperatorVisitor 1) { - method.load(depth); - method.invoke(ScriptObject.GET_PROTO_DEPTH); - } else { - method.invoke(ScriptObject.GET_PROTO); - } + invokeGetProto(depth); if (swap) { method.swap(); } } } + private void invokeGetProto(final int depth) { + assert depth > 0; + if (depth > 1) { + method.load(depth); + method.invoke(ScriptObject.GET_PROTO_DEPTH); + } else { + method.invoke(ScriptObject.GET_PROTO); + } + } + /** * Generate code that loads this node to the stack, not constraining its type * @@ -1386,12 +1387,7 @@ final class CodeGenerator extends NodeOperatorVisitor 1) { - method.load(count); - method.invoke(ScriptObject.GET_PROTO_DEPTH); - } else { - method.invoke(ScriptObject.GET_PROTO); - } + invokeGetProto(count); method.storeCompilerConstant(SCOPE); } @@ -1444,20 +1440,22 @@ final class CodeGenerator extends NodeOperatorVisitor= 0; + method.load(depth); + method.load(getProgramPoint()); loadArgs(args); } + @Override void consumeStack() { final Type[] paramTypes = method.getTypesFromStack(args.size()); @@ -1466,13 +1464,14 @@ final class CodeGenerator extends NodeOperatorVisitor clazz, final boolean isOptimismHandler) { recovery.joinFromTry(entry.getStack(), isOptimismHandler); + final String typeDescriptor = clazz == null ? null : CompilerConstants.className(clazz); method.visitTryCatchBlock(entry.getLabel(), exit.getLabel(), recovery.getLabel(), typeDescriptor); } @@ -727,7 +729,7 @@ public class MethodEmitter { * @param clazz exception class */ void _try(final Label entry, final Label exit, final Label recovery, final Class clazz) { - _try(entry, exit, recovery, CompilerConstants.className(clazz), clazz == UnwarrantedOptimismException.class); + _try(entry, exit, recovery, clazz, clazz == UnwarrantedOptimismException.class); } /** diff --git a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java index eb4758f873d..0d7e54995c1 100644 --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java @@ -25,6 +25,7 @@ package jdk.nashorn.internal.codegen; +import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC; import java.util.Arrays; @@ -32,39 +33,52 @@ import java.util.EnumSet; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; +import jdk.nashorn.internal.runtime.options.Options; /** - * A scope call or get operation that can be shared by several callsites. This generates a static + * A scope call or get operation that can be shared by several call sites. This generates a static * method that wraps the invokedynamic instructions to get or call scope variables. - * The rationale for this is that initial linking of invokedynamic callsites is expensive, - * so by sharing them we can reduce startup overhead and allow very large scripts to run that otherwise wouldn't. + * The reason for this is to reduce memory footprint and initial linking overhead of huge scripts. * - *

Static methods generated by this class expect two parameters in addition to the parameters of the - * function call: The current scope object and the depth of the target scope relative to the scope argument - * for when this is known at compile-time (fast-scope access).

+ *

Static methods generated by this class expect three parameters in addition to the parameters of the + * function call: The current scope object, the depth of the target scope relative to the scope argument, + * and the program point in case the target operation is optimistic.

* - *

The second argument may be -1 for non-fast-scope symbols, in which case the scope chain is checked - * for each call. This may cause callsite invalidation when the shared method is used from different - * scopes, but such sharing of non-fast scope calls may still be necessary for very large scripts.

+ *

Optimistic operations are called with program point 0. If an UnwarrentedOptimismException + * is thrown, it is caught by the shared call method and rethrown with the program point of the invoking call site.

* - *

Scope calls must not be shared between normal callsites and callsites contained in a with - * statement as this condition is not handled by current guards and will cause a runtime error.

+ *

Shared scope calls are not used if the scope contains a with statement or a call to + * eval.

*/ class SharedScopeCall { - /** Threshold for using shared scope calls with fast scope access. */ - public static final int FAST_SCOPE_CALL_THRESHOLD = 4; - /** Threshold for using shared scope calls with slow scope access. */ - public static final int SLOW_SCOPE_CALL_THRESHOLD = 500; - /** Threshold for using shared scope gets with fast scope access. */ - public static final int FAST_SCOPE_GET_THRESHOLD = 200; + /** + * Threshold for using shared scope function calls. + */ + public static final int SHARED_CALL_THRESHOLD = + Options.getIntProperty("nashorn.shared.scope.call.threshold", 5); + /** + * Threshold for using shared scope variable getter. This is higher than for calls as lower values + * degrade performance on many scripts. + */ + public static final int SHARED_GET_THRESHOLD = + Options.getIntProperty("nashorn.shared.scope.get.threshold", 100); - final Type valueType; - final Symbol symbol; - final Type returnType; - final Type[] paramTypes; - final int flags; - final boolean isCall; + private static final CompilerConstants.Call REPLACE_PROGRAM_POINT = virtualCallNoLookup( + UnwarrantedOptimismException.class, "replaceProgramPoint", + UnwarrantedOptimismException.class, int.class); + + /** Number of fixed parameters */ + private static final int FIXED_PARAM_COUNT = 3; + + private final Type valueType; + private final Symbol symbol; + private final Type returnType; + private final Type[] paramTypes; + private final int flags; + private final boolean isCall; + private final boolean isOptimistic; private CompileUnit compileUnit; private String methodName; private String staticSignature; @@ -77,21 +91,22 @@ class SharedScopeCall { * @param returnType the return type * @param paramTypes the function parameter types * @param flags the callsite flags + * @param isOptimistic whether target call is optimistic and we need to handle UnwarrentedOptimismException */ - SharedScopeCall(final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, final int flags) { + SharedScopeCall(final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, + final int flags, final boolean isOptimistic) { this.symbol = symbol; this.valueType = valueType; this.returnType = returnType; this.paramTypes = paramTypes; - assert (flags & CALLSITE_OPTIMISTIC) == 0; this.flags = flags; - // If paramTypes is not null this is a call, otherwise it's just a get. - this.isCall = paramTypes != null; + this.isCall = paramTypes != null; // If paramTypes is not null this is a call, otherwise it's just a get. + this.isOptimistic = isOptimistic; } @Override public int hashCode() { - return symbol.hashCode() ^ returnType.hashCode() ^ Arrays.hashCode(paramTypes) ^ flags; + return symbol.hashCode() ^ returnType.hashCode() ^ Arrays.hashCode(paramTypes) ^ flags ^ Boolean.hashCode(isOptimistic); } @Override @@ -101,7 +116,8 @@ class SharedScopeCall { return symbol.equals(c.symbol) && flags == c.flags && returnType.equals(c.returnType) - && Arrays.equals(paramTypes, c.paramTypes); + && Arrays.equals(paramTypes, c.paramTypes) + && isOptimistic == c.isOptimistic; } return false; } @@ -119,10 +135,9 @@ class SharedScopeCall { /** * Generate the invoke instruction for this shared scope call. * @param method the method emitter - * @return the method emitter */ - public MethodEmitter generateInvoke(final MethodEmitter method) { - return method.invokestatic(compileUnit.getUnitClassName(), methodName, getStaticSignature()); + public void generateInvoke(final MethodEmitter method) { + method.invokestatic(compileUnit.getUnitClassName(), methodName, getStaticSignature()); } /** @@ -140,51 +155,79 @@ class SharedScopeCall { final MethodEmitter method = classEmitter.method(methodFlags, methodName, getStaticSignature()); method.begin(); - // Load correct scope by calling getProto() on the scope argument as often as specified - // by the second argument. - final Label parentLoopStart = new Label("parent_loop_start"); - final Label parentLoopDone = new Label("parent_loop_done"); + // Load correct scope by calling getProto(int) on the scope argument with the supplied depth argument method.load(Type.OBJECT, 0); - method.label(parentLoopStart); method.load(Type.INT, 1); - method.iinc(1, -1); - method.ifle(parentLoopDone); - method.invoke(ScriptObject.GET_PROTO); - method._goto(parentLoopStart); - method.label(parentLoopDone); + method.invoke(ScriptObject.GET_PROTO_DEPTH); - assert !isCall || valueType.isObject(); // Callables are always objects - // If flags are optimistic, but we're doing a call, remove optimistic flags from the getter, as they obviously - // only apply to the call. - method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall, false); + assert !isCall || valueType.isObject(); // Callables are always loaded as object + + // Labels for catch of UnsupportedOptimismException + final Label beginTry; + final Label endTry; + final Label catchLabel; + + if(isOptimistic) { + beginTry = new Label("begin_try"); + endTry = new Label("end_try"); + catchLabel = new Label("catch_label"); + method.label(beginTry); + method._try(beginTry, endTry, catchLabel, UnwarrantedOptimismException.class, false); + } else { + beginTry = endTry = catchLabel = null; + } + + // If this is an optimistic get we set the optimistic flag but don't set the program point, + // which implies a program point of 0. If optimism fails we'll replace it with the actual + // program point which caller supplied as third argument. + final int getFlags = isOptimistic && !isCall ? flags | CALLSITE_OPTIMISTIC : flags; + method.dynamicGet(valueType, symbol.getName(), getFlags, isCall, false); // If this is a get we're done, otherwise call the value as function. if (isCall) { method.convert(Type.OBJECT); // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. method.loadUndefined(Type.OBJECT); - int slot = 2; + int slot = FIXED_PARAM_COUNT; for (final Type type : paramTypes) { method.load(type, slot); slot += type.getSlots(); } - // Shared scope calls disabled in optimistic world. TODO is this right? - method.dynamicCall(returnType, 2 + paramTypes.length, flags, symbol.getName()); + + // Same as above, set optimistic flag but leave program point as 0. + final int callFlags = isOptimistic ? flags | CALLSITE_OPTIMISTIC : flags; + + method.dynamicCall(returnType, 2 + paramTypes.length, callFlags, symbol.getName()); + + } + + if (isOptimistic) { + method.label(endTry); } method._return(returnType); + + if (isOptimistic) { + // We caught a UnwarrantedOptimismException, replace 0 program point with actual program point + method._catch(catchLabel); + method.load(Type.INT, 2); + method.invoke(REPLACE_PROGRAM_POINT); + method.athrow(); + } + method.end(); } private String getStaticSignature() { if (staticSignature == null) { if (paramTypes == null) { - staticSignature = Type.getMethodDescriptor(returnType, Type.typeFor(ScriptObject.class), Type.INT); + staticSignature = Type.getMethodDescriptor(returnType, Type.typeFor(ScriptObject.class), Type.INT, Type.INT); } else { - final Type[] params = new Type[paramTypes.length + 2]; + final Type[] params = new Type[paramTypes.length + FIXED_PARAM_COUNT]; params[0] = Type.typeFor(ScriptObject.class); params[1] = Type.INT; - System.arraycopy(paramTypes, 0, params, 2, paramTypes.length); + params[2] = Type.INT; + System.arraycopy(paramTypes, 0, params, FIXED_PARAM_COUNT, paramTypes.length); staticSignature = Type.getMethodDescriptor(returnType, params); } } diff --git a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java index 2b0ac1b7f55..40b8c67d9a6 100644 --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java @@ -1229,9 +1229,8 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable { * @return proto at given depth */ public final ScriptObject getProto(final int n) { - assert n > 0; - ScriptObject p = getProto(); - for (int i = n; --i > 0;) { + ScriptObject p = this; + for (int i = n; i > 0; i--) { p = p.getProto(); } return p; diff --git a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/UnwarrantedOptimismException.java b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/UnwarrantedOptimismException.java index a92a0cccad8..f9deaf0fccc 100644 --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/UnwarrantedOptimismException.java +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/UnwarrantedOptimismException.java @@ -153,12 +153,15 @@ public final class UnwarrantedOptimismException extends RuntimeException { } /** - * Check if we ended up with a primitive return value (even though it may be - * too wide for what we tried to do, e.g. double instead of int) - * @return true if return value is primitive + * Return a new {@code UnwarrantedOptimismException} with the same return value and the + * new program point. + * + * @param newProgramPoint new new program point + * @return the new exception instance */ - public boolean hasPrimitiveReturnValue() { - return returnValue instanceof Number || returnValue instanceof Boolean; + public UnwarrantedOptimismException replaceProgramPoint(final int newProgramPoint) { + assert isValid(newProgramPoint); + return new UnwarrantedOptimismException(returnValue, newProgramPoint, returnType); } @Override diff --git a/test/nashorn/script/basic/JDK-8069338.js b/test/nashorn/script/basic/JDK-8069338.js new file mode 100644 index 00000000000..d242f13937a --- /dev/null +++ b/test/nashorn/script/basic/JDK-8069338.js @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017, 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. + * + * 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. + */ + +/** + * JDK-8069338: Implement sharedScopeCall for optimistic types + * + * @test + * @run + */ + +var c = 0; +var n = 1; + +function f() { + return c++ > 10 ? 'f' : 1; +} + +function h() { + var x = 0; + x += n; + x += n; + x += n; + x += n; + x += n; + x += n; + x += n; + x += n; + x += n; + x += n; + x += n; + n = 'b'; + x += n; + x += n; + x += n; + x += n; + x += n; + x += n; + return x; +} + +function g() { + var x = 0; + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + x += f(); + return x; +} + +Assert.assertEquals(h(), '11bbbbbb'); +Assert.assertEquals(g(), '11ffffff'); +