# HG changeset patch # User hannesw # Date 1513089498 -3600 # Node ID fa5a47cad0c9faf19b8eff332e0413acdcee3fbb # Parent aadc02050d3b6bca9335c6767dd3e776e77e8625 8069338: Implement sharedScopeCall for optimistic types Reviewed-by: attila, sundar diff -r aadc02050d3b -r fa5a47cad0c9 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Tue Dec 12 18:40:31 2017 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Tue Dec 12 15:38:18 2017 +0100 @@ -346,28 +346,30 @@ assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; final int flags = getScopeCallSiteFlags(symbol); - if (isFastScope(symbol)) { - // Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope. - if (symbol.getUseCount() > 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(); - } + 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 { - //slow scope load, we have no proto depth - new LoadScopeVar(identNode, resultBounds, flags).emit(); + // 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 @@ throw new AssertionError(); } - private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) { - assert isFastScope(symbol); - method.load(getScopeProtoDepth(lc.getCurrentBlock(), symbol)); - return lc.getScopeGet(unit, symbol, valueType, flags).generateInvoke(method); - } - private class LoadScopeVar extends OptimisticOperation { final IdentNode identNode; private final int flags; @@ -551,18 +547,23 @@ if (swap) { method.swap(); } - if (depth > 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 @@ return; } method.loadCompilerConstant(SCOPE); - if (count > 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 CodeGeneratorLexicalContext codegenLexicalContext = lc; function.accept(new SimpleNodeVisitor() { + private MethodEmitter sharedScopeCall(final IdentNode identNode, final int flags) { final Symbol symbol = identNode.getSymbol(); - final boolean isFastScope = isFastScope(symbol); + assert isFastScope(symbol); + new OptimisticOperation(callNode, resultBounds) { @Override void loadStack() { method.loadCompilerConstant(SCOPE); - if (isFastScope) { - method.load(getScopeProtoDepth(currentBlock, symbol)); - } else { - method.load(-1); // Bypass fast-scope code in shared callsite - } + final int depth = getScopeProtoDepth(currentBlock, symbol); + assert depth >= 0; + method.load(depth); + method.load(getProgramPoint()); loadArgs(args); } + @Override void consumeStack() { final Type[] paramTypes = method.getTypesFromStack(args.size()); @@ -1466,13 +1464,14 @@ for(int i = 0; i < paramTypes.length; ++i) { paramTypes[i] = Type.generic(paramTypes[i]); } - // As shared scope calls are only used in non-optimistic compilation, we switch from using - // TypeBounds to just a single definitive type, resultBounds.widest. + + final Type resultType = isOptimistic ? getOptimisticCoercedType() : resultBounds.widest; final SharedScopeCall scopeCall = codegenLexicalContext.getScopeCall(unit, symbol, - identNode.getType(), resultBounds.widest, paramTypes, flags); + identNode.getType(), resultType, paramTypes, flags, isOptimistic); scopeCall.generateInvoke(method); } }.emit(); + return method; } @@ -1573,15 +1572,10 @@ final int flags = getScopeCallSiteFlags(symbol); final int useCount = symbol.getUseCount(); - // Threshold for generating shared scope callsite is lower for fast scope symbols because we know - // we can dial in the correct scope. However, we also need to enable it for non-fast scopes to - // support huge scripts like mandreel.js. + // We only use shared scope calls for fast scopes if (callNode.isEval()) { evalCall(node, flags); - } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD - || !isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD - || CodeGenerator.this.lc.inDynamicScope() - || callNode.isOptimistic()) { + } else if (!isFastScope(symbol) || symbol.getUseCount() < SharedScopeCall.SHARED_CALL_THRESHOLD) { scopeCall(node, flags); } else { sharedScopeCall(node, flags); @@ -4650,7 +4644,7 @@ } private abstract class OptimisticOperation { - private final boolean isOptimistic; + final boolean isOptimistic; // expression and optimistic are the same reference private final Expression expression; private final Optimistic optimistic; @@ -4966,7 +4960,7 @@ * affect it. * @return */ - private Type getOptimisticCoercedType() { + Type getOptimisticCoercedType() { final Type optimisticType = expression.getType(); assert resultBounds.widest.widerThan(optimisticType); final Type narrowest = resultBounds.narrowest; diff -r aadc02050d3b -r fa5a47cad0c9 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java Tue Dec 12 18:40:31 2017 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java Tue Dec 12 15:38:18 2017 +0100 @@ -184,10 +184,14 @@ * @param returnType the return type * @param paramTypes the parameter types * @param flags the callsite flags + * @param isOptimistic is this an optimistic call * @return an object representing a shared scope call */ - SharedScopeCall getScopeCall(final CompileUnit unit, final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, final int flags) { - final SharedScopeCall scopeCall = new SharedScopeCall(symbol, valueType, returnType, paramTypes, flags); + SharedScopeCall getScopeCall(final CompileUnit unit, final Symbol symbol, final Type valueType, + final Type returnType, final Type[] paramTypes, final int flags, + final boolean isOptimistic) { + final SharedScopeCall scopeCall = new SharedScopeCall(symbol, valueType, returnType, paramTypes, flags, + isOptimistic); if (scopeCalls.containsKey(scopeCall)) { return scopeCalls.get(scopeCall); } @@ -203,10 +207,12 @@ * @param symbol the symbol * @param valueType the type of the variable * @param flags the callsite flags - * @return an object representing a shared scope call + * @param isOptimistic is this an optimistic get + * @return an object representing a shared scope get */ - SharedScopeCall getScopeGet(final CompileUnit unit, final Symbol symbol, final Type valueType, final int flags) { - return getScopeCall(unit, symbol, valueType, valueType, null, flags); + SharedScopeCall getScopeGet(final CompileUnit unit, final Symbol symbol, final Type valueType, final int flags, + final boolean isOptimistic) { + return getScopeCall(unit, symbol, valueType, valueType, null, flags, isOptimistic); } void onEnterBlock(final Block block) { diff -r aadc02050d3b -r fa5a47cad0c9 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java Tue Dec 12 18:40:31 2017 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java Tue Dec 12 15:38:18 2017 +0100 @@ -702,19 +702,21 @@ } pushType(Type.typeFor(Throwable.class)); } + /** * Start a try/catch block. * - * @param entry start label for try - * @param exit end label for try - * @param recovery start label for catch - * @param typeDescriptor type descriptor for exception + * @param entry start label for try + * @param exit end label for try + * @param recovery start label for catch + * @param clazz exception class or null for any Throwable * @param isOptimismHandler true if this is a hander for {@code UnwarrantedOptimismException}. Normally joining on a * catch handler kills temporary variables, but optimism handlers are an exception, as they need to capture * temporaries as well, so they must remain live. */ - private void _try(final Label entry, final Label exit, final Label recovery, final String typeDescriptor, final boolean isOptimismHandler) { + void _try(final Label entry, final Label exit, final Label recovery, final Class> 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 @@ * @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 -r aadc02050d3b -r fa5a47cad0c9 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java Tue Dec 12 18:40:31 2017 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java Tue Dec 12 15:38:18 2017 +0100 @@ -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 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
.