Wed, 05 Nov 2014 12:34:06 +0100
8057825: Bug in apply specialization - if an apply specialization that is available doesn't fit, a new one wouldn't be installed, if the new code generated as a specialization didn't manage to do the apply specialization. Basically changing a conditional to an unconditional.
Reviewed-by: attila, hannesw
1.1 --- a/src/jdk/nashorn/internal/codegen/ApplySpecialization.java Mon Nov 03 14:59:34 2014 +0100 1.2 +++ b/src/jdk/nashorn/internal/codegen/ApplySpecialization.java Wed Nov 05 12:34:06 2014 +0100 1.3 @@ -29,6 +29,7 @@ 1.4 import static jdk.nashorn.internal.codegen.CompilerConstants.EXPLODED_ARGUMENT_PREFIX; 1.5 1.6 import java.lang.invoke.MethodType; 1.7 +import java.net.URL; 1.8 import java.util.ArrayDeque; 1.9 import java.util.ArrayList; 1.10 import java.util.Deque; 1.11 @@ -93,6 +94,8 @@ 1.12 1.13 private final Deque<List<IdentNode>> explodedArguments = new ArrayDeque<>(); 1.14 1.15 + private final Deque<MethodType> callSiteTypes = new ArrayDeque<>(); 1.16 + 1.17 private static final String ARGUMENTS = ARGUMENTS_VAR.symbolName(); 1.18 1.19 /** 1.20 @@ -118,86 +121,108 @@ 1.21 return context.getLogger(this.getClass()); 1.22 } 1.23 1.24 + @SuppressWarnings("serial") 1.25 + private static class TransformFailedException extends RuntimeException { 1.26 + TransformFailedException(final FunctionNode fn, final String message) { 1.27 + super(massageURL(fn.getSource().getURL()) + '.' + fn.getName() + " => " + message, null, false, false); 1.28 + } 1.29 + } 1.30 + 1.31 + @SuppressWarnings("serial") 1.32 + private static class AppliesFoundException extends RuntimeException { 1.33 + AppliesFoundException() { 1.34 + super("applies_found", null, false, false); 1.35 + } 1.36 + } 1.37 + 1.38 + private static final AppliesFoundException HAS_APPLIES = new AppliesFoundException(); 1.39 + 1.40 + private boolean hasApplies(final FunctionNode functionNode) { 1.41 + try { 1.42 + functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 1.43 + @Override 1.44 + public boolean enterCallNode(final CallNode callNode) { 1.45 + if (isApply(callNode)) { 1.46 + throw HAS_APPLIES; 1.47 + } 1.48 + return true; 1.49 + } 1.50 + }); 1.51 + } catch (final AppliesFoundException e) { 1.52 + return true; 1.53 + } 1.54 + 1.55 + log.fine("There are no applies in ", DebugLogger.quote(functionNode.getName()), " - nothing to do."); 1.56 + return false; // no applies 1.57 + } 1.58 + 1.59 /** 1.60 * Arguments may only be used as args to the apply. Everything else is disqualified 1.61 * We cannot control arguments if they escape from the method and go into an unknown 1.62 * scope, thus we are conservative and treat any access to arguments outside the 1.63 * apply call as a case of "we cannot apply the optimization". 1.64 - * 1.65 - * @return true if arguments escape 1.66 */ 1.67 - private boolean argumentsEscape(final FunctionNode functionNode) { 1.68 - 1.69 - @SuppressWarnings("serial") 1.70 - final UnsupportedOperationException uoe = new UnsupportedOperationException() { 1.71 - @Override 1.72 - public synchronized Throwable fillInStackTrace() { 1.73 - return null; 1.74 - } 1.75 - }; 1.76 + private void checkValidTransform(final FunctionNode functionNode) { 1.77 1.78 final Set<Expression> argumentsFound = new HashSet<>(); 1.79 final Deque<Set<Expression>> stack = new ArrayDeque<>(); 1.80 + 1.81 //ensure that arguments is only passed as arg to apply 1.82 - try { 1.83 - functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 1.84 - private boolean isCurrentArg(final Expression expr) { 1.85 - return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call 1.86 - } 1.87 + functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 1.88 1.89 - private boolean isArguments(final Expression expr) { 1.90 - if (expr instanceof IdentNode && ARGUMENTS.equals(((IdentNode)expr).getName())) { 1.91 - argumentsFound.add(expr); 1.92 + private boolean isCurrentArg(final Expression expr) { 1.93 + return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call 1.94 + } 1.95 + 1.96 + private boolean isArguments(final Expression expr) { 1.97 + if (expr instanceof IdentNode && ARGUMENTS.equals(((IdentNode)expr).getName())) { 1.98 + argumentsFound.add(expr); 1.99 + return true; 1.100 + } 1.101 + return false; 1.102 + } 1.103 + 1.104 + private boolean isParam(final String name) { 1.105 + for (final IdentNode param : functionNode.getParameters()) { 1.106 + if (param.getName().equals(name)) { 1.107 return true; 1.108 } 1.109 - return false; 1.110 } 1.111 + return false; 1.112 + } 1.113 1.114 - private boolean isParam(final String name) { 1.115 - for (final IdentNode param : functionNode.getParameters()) { 1.116 - if (param.getName().equals(name)) { 1.117 - return true; 1.118 - } 1.119 + @Override 1.120 + public Node leaveIdentNode(final IdentNode identNode) { 1.121 + if (isParam(identNode.getName())) { 1.122 + throw new TransformFailedException(lc.getCurrentFunction(), "parameter: " + identNode.getName()); 1.123 + } 1.124 + // it's OK if 'argument' occurs as the current argument of an apply 1.125 + if (isArguments(identNode) && !isCurrentArg(identNode)) { 1.126 + throw new TransformFailedException(lc.getCurrentFunction(), "is 'arguments': " + identNode.getName()); 1.127 + } 1.128 + return identNode; 1.129 + } 1.130 + 1.131 + @Override 1.132 + public boolean enterCallNode(final CallNode callNode) { 1.133 + final Set<Expression> callArgs = new HashSet<>(); 1.134 + if (isApply(callNode)) { 1.135 + final List<Expression> argList = callNode.getArgs(); 1.136 + if (argList.size() != 2 || !isArguments(argList.get(argList.size() - 1))) { 1.137 + throw new TransformFailedException(lc.getCurrentFunction(), "argument pattern not matched: " + argList); 1.138 } 1.139 - return false; 1.140 + callArgs.addAll(callNode.getArgs()); 1.141 } 1.142 + stack.push(callArgs); 1.143 + return true; 1.144 + } 1.145 1.146 - @Override 1.147 - public Node leaveIdentNode(final IdentNode identNode) { 1.148 - if (isParam(identNode.getName()) || isArguments(identNode) && !isCurrentArg(identNode)) { 1.149 - throw uoe; //avoid filling in stack trace 1.150 - } 1.151 - return identNode; 1.152 - } 1.153 - 1.154 - @Override 1.155 - public boolean enterCallNode(final CallNode callNode) { 1.156 - final Set<Expression> callArgs = new HashSet<>(); 1.157 - if (isApply(callNode)) { 1.158 - final List<Expression> argList = callNode.getArgs(); 1.159 - if (argList.size() != 2 || !isArguments(argList.get(argList.size() - 1))) { 1.160 - throw new UnsupportedOperationException(); 1.161 - } 1.162 - callArgs.addAll(callNode.getArgs()); 1.163 - } 1.164 - stack.push(callArgs); 1.165 - return true; 1.166 - } 1.167 - 1.168 - @Override 1.169 - public Node leaveCallNode(final CallNode callNode) { 1.170 - stack.pop(); 1.171 - return callNode; 1.172 - } 1.173 - }); 1.174 - } catch (final UnsupportedOperationException e) { 1.175 - if (!argumentsFound.isEmpty()) { 1.176 - log.fine("'arguments' is used but escapes, or is reassigned in '" + functionNode.getName() + "'. Aborting"); 1.177 + @Override 1.178 + public Node leaveCallNode(final CallNode callNode) { 1.179 + stack.pop(); 1.180 + return callNode; 1.181 } 1.182 - return true; //bad 1.183 - } 1.184 - 1.185 - return false; 1.186 + }); 1.187 } 1.188 1.189 @Override 1.190 @@ -224,12 +249,14 @@ 1.191 1.192 final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); 1.193 1.194 - log.fine("Transformed ", 1.195 - callNode, 1.196 - " from apply to call => ", 1.197 - newCallNode, 1.198 - " in ", 1.199 - DebugLogger.quote(lc.getCurrentFunction().getName())); 1.200 + if (log.isEnabled()) { 1.201 + log.fine("Transformed ", 1.202 + callNode, 1.203 + " from apply to call => ", 1.204 + newCallNode, 1.205 + " in ", 1.206 + DebugLogger.quote(lc.getCurrentFunction().getName())); 1.207 + } 1.208 1.209 return newCallNode; 1.210 } 1.211 @@ -237,12 +264,12 @@ 1.212 return callNode; 1.213 } 1.214 1.215 - private boolean pushExplodedArgs(final FunctionNode functionNode) { 1.216 + private void pushExplodedArgs(final FunctionNode functionNode) { 1.217 int start = 0; 1.218 1.219 final MethodType actualCallSiteType = compiler.getCallSiteType(functionNode); 1.220 if (actualCallSiteType == null) { 1.221 - return false; 1.222 + throw new TransformFailedException(lc.getCurrentFunction(), "No callsite type"); 1.223 } 1.224 assert actualCallSiteType.parameterType(actualCallSiteType.parameterCount() - 1) != Object[].class : "error vararg callsite passed to apply2call " + functionNode.getName() + " " + actualCallSiteType; 1.225 1.226 @@ -264,8 +291,8 @@ 1.227 } 1.228 } 1.229 1.230 + callSiteTypes.push(actualCallSiteType); 1.231 explodedArguments.push(newParams); 1.232 - return true; 1.233 } 1.234 1.235 @Override 1.236 @@ -288,11 +315,30 @@ 1.237 return false; 1.238 } 1.239 1.240 - if (argumentsEscape(functionNode)) { 1.241 + if (!hasApplies(functionNode)) { 1.242 return false; 1.243 } 1.244 1.245 - return pushExplodedArgs(functionNode); 1.246 + if (log.isEnabled()) { 1.247 + log.info("Trying to specialize apply to call in '", 1.248 + functionNode.getName(), 1.249 + "' params=", 1.250 + functionNode.getParameters(), 1.251 + " id=", 1.252 + functionNode.getId(), 1.253 + " source=", 1.254 + massageURL(functionNode.getSource().getURL())); 1.255 + } 1.256 + 1.257 + try { 1.258 + checkValidTransform(functionNode); 1.259 + pushExplodedArgs(functionNode); 1.260 + } catch (final TransformFailedException e) { 1.261 + log.info("Failure: ", e.getMessage()); 1.262 + return false; 1.263 + } 1.264 + 1.265 + return true; 1.266 } 1.267 1.268 /** 1.269 @@ -300,8 +346,8 @@ 1.270 * @return true if successful, false otherwise 1.271 */ 1.272 @Override 1.273 - public Node leaveFunctionNode(final FunctionNode functionNode0) { 1.274 - FunctionNode newFunctionNode = functionNode0; 1.275 + public Node leaveFunctionNode(final FunctionNode functionNode) { 1.276 + FunctionNode newFunctionNode = functionNode; 1.277 final String functionName = newFunctionNode.getName(); 1.278 1.279 if (changed.contains(newFunctionNode.getId())) { 1.280 @@ -310,17 +356,18 @@ 1.281 setParameters(lc, explodedArguments.peek()); 1.282 1.283 if (log.isEnabled()) { 1.284 - log.info("Successfully specialized apply to call in '", 1.285 + log.info("Success: ", 1.286 + massageURL(newFunctionNode.getSource().getURL()), 1.287 + '.', 1.288 functionName, 1.289 - " params=", 1.290 - explodedArguments.peek(), 1.291 "' id=", 1.292 newFunctionNode.getId(), 1.293 - " source=", 1.294 - newFunctionNode.getSource().getURL()); 1.295 + " params=", 1.296 + callSiteTypes.peek()); 1.297 } 1.298 } 1.299 1.300 + callSiteTypes.pop(); 1.301 explodedArguments.pop(); 1.302 1.303 return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED); 1.304 @@ -331,4 +378,15 @@ 1.305 return f instanceof AccessNode && "apply".equals(((AccessNode)f).getProperty()); 1.306 } 1.307 1.308 + private static String massageURL(final URL url) { 1.309 + if (url == null) { 1.310 + return "<null>"; 1.311 + } 1.312 + final String str = url.toString(); 1.313 + final int slash = str.lastIndexOf('/'); 1.314 + if (slash == -1) { 1.315 + return str; 1.316 + } 1.317 + return str.substring(slash + 1); 1.318 + } 1.319 }
2.1 --- a/src/jdk/nashorn/internal/codegen/CodeGenerator.java Mon Nov 03 14:59:34 2014 +0100 2.2 +++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java Wed Nov 05 12:34:06 2014 +0100 2.3 @@ -615,7 +615,6 @@ 2.4 2.5 static final TypeBounds UNBOUNDED = new TypeBounds(Type.UNKNOWN, Type.OBJECT); 2.6 static final TypeBounds INT = exact(Type.INT); 2.7 - static final TypeBounds NUMBER = exact(Type.NUMBER); 2.8 static final TypeBounds OBJECT = exact(Type.OBJECT); 2.9 static final TypeBounds BOOLEAN = exact(Type.BOOLEAN); 2.10 2.11 @@ -3894,7 +3893,7 @@ 2.12 2.13 private static boolean isRhsZero(final BinaryNode binaryNode) { 2.14 final Expression rhs = binaryNode.rhs(); 2.15 - return rhs instanceof LiteralNode && INT_ZERO.equals(((LiteralNode)rhs).getValue()); 2.16 + return rhs instanceof LiteralNode && INT_ZERO.equals(((LiteralNode<?>)rhs).getValue()); 2.17 } 2.18 2.19 private void loadBIT_XOR(final BinaryNode binaryNode) {
3.1 --- a/src/jdk/nashorn/internal/codegen/types/Type.java Mon Nov 03 14:59:34 2014 +0100 3.2 +++ b/src/jdk/nashorn/internal/codegen/types/Type.java Wed Nov 05 12:34:06 2014 +0100 3.3 @@ -1155,6 +1155,10 @@ 3.4 return type; 3.5 } 3.6 3.7 + /** 3.8 + * Read resolve 3.9 + * @return resolved type 3.10 + */ 3.11 protected final Object readResolve() { 3.12 return Type.typeFor(clazz); 3.13 }
4.1 --- a/src/jdk/nashorn/internal/objects/NativeString.java Mon Nov 03 14:59:34 2014 +0100 4.2 +++ b/src/jdk/nashorn/internal/objects/NativeString.java Wed Nov 05 12:34:06 2014 +0100 4.3 @@ -29,6 +29,7 @@ 4.4 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 4.5 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 4.6 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 4.7 + 4.8 import java.lang.invoke.MethodHandle; 4.9 import java.lang.invoke.MethodHandles; 4.10 import java.lang.invoke.MethodType; 4.11 @@ -1380,7 +1381,6 @@ 4.12 * sequence and that we are in range 4.13 */ 4.14 private static final class CharCodeAtLinkLogic extends SpecializedFunction.LinkLogic { 4.15 - 4.16 private static final CharCodeAtLinkLogic INSTANCE = new CharCodeAtLinkLogic(); 4.17 4.18 @Override
5.1 --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon Nov 03 14:59:34 2014 +0100 5.2 +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Nov 05 12:34:06 2014 +0100 5.3 @@ -26,6 +26,7 @@ 5.4 package jdk.nashorn.internal.runtime; 5.5 5.6 import static jdk.nashorn.internal.lookup.Lookup.MH; 5.7 + 5.8 import java.io.IOException; 5.9 import java.lang.invoke.MethodHandle; 5.10 import java.lang.invoke.MethodHandles; 5.11 @@ -727,7 +728,7 @@ 5.12 5.13 assert existingBest != null; 5.14 //we are calling a vararg method with real args 5.15 - boolean applyToCall = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType); 5.16 + boolean varArgWithRealArgs = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType); 5.17 5.18 //if the best one is an apply to call, it has to match the callsite exactly 5.19 //or we need to regenerate 5.20 @@ -736,14 +737,16 @@ 5.21 if (best != null) { 5.22 return best; 5.23 } 5.24 - applyToCall = true; 5.25 + varArgWithRealArgs = true; 5.26 } 5.27 5.28 - if (applyToCall) { 5.29 + if (varArgWithRealArgs) { 5.30 + // special case: we had an apply to call, but we failed to make it fit. 5.31 + // Try to generate a specialized one for this callsite. It may 5.32 + // be another apply to call specialization, or it may not, but whatever 5.33 + // it is, it is a specialization that is guaranteed to fit 5.34 final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false); 5.35 - if ((fnInit.getFlags() & FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION) != 0) { //did the specialization work 5.36 - existingBest = addCode(fnInit, callSiteType); 5.37 - } 5.38 + existingBest = addCode(fnInit, callSiteType); 5.39 } 5.40 5.41 return existingBest;
6.1 --- a/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java Mon Nov 03 14:59:34 2014 +0100 6.2 +++ b/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java Wed Nov 05 12:34:06 2014 +0100 6.3 @@ -36,12 +36,7 @@ 6.4 * Handle arrays where the index is very large. 6.5 */ 6.6 class SparseArrayData extends ArrayData { 6.7 - static final long MAX_DENSE_LENGTH = 16 * 512 * 1024; 6.8 - 6.9 - static { 6.10 - // we must break into sparse arrays before we require long indexes 6.11 - assert MAX_DENSE_LENGTH <= Integer.MAX_VALUE; 6.12 - } 6.13 + static final int MAX_DENSE_LENGTH = 8 * 1024 * 1024; 6.14 6.15 /** Underlying array. */ 6.16 private ArrayData underlying;
7.1 --- a/src/jdk/nashorn/internal/runtime/events/RecompilationEvent.java Mon Nov 03 14:59:34 2014 +0100 7.2 +++ b/src/jdk/nashorn/internal/runtime/events/RecompilationEvent.java Wed Nov 05 12:34:06 2014 +0100 7.3 @@ -47,7 +47,7 @@ 7.4 * @param level logging level 7.5 * @param rewriteException rewriteException wrapped by this RuntimEvent 7.6 * @param returnValue rewriteException return value - as we don't want to make 7.7 - * {@link RewriteException#getReturnValueNonDestructive()} public, we pass it as 7.8 + * {@code RewriteException.getReturnValueNonDestructive()} public, we pass it as 7.9 * an extra parameter, rather than querying the getter from another package. 7.10 */ 7.11 public RecompilationEvent(final Level level, final RewriteException rewriteException, final Object returnValue) {
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 8.2 +++ b/test/script/basic/JDK-8057825.js Wed Nov 05 12:34:06 2014 +0100 8.3 @@ -0,0 +1,45 @@ 8.4 +/* 8.5 + * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. 8.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 8.7 + * 8.8 + * This code is free software; you can redistribute it and/or modify it 8.9 + * under the terms of the GNU General Public License version 2 only, as 8.10 + * published by the Free Software Foundation. 8.11 + * 8.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 8.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 8.15 + * version 2 for more details (a copy is included in the LICENSE file that 8.16 + * accompanied this code). 8.17 + * 8.18 + * You should have received a copy of the GNU General Public License version 8.19 + * 2 along with this work; if not, write to the Free Software Foundation, 8.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 8.21 + * 8.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 8.23 + * or visit www.oracle.com if you need additional information or have any 8.24 + * questions. 8.25 + */ 8.26 + 8.27 +/** 8.28 + * JDK-8057825 : A failed apply to call generation should NOT reuse the 8.29 + * best apply to call generation so far - because it may not fit!!! 8.30 + * 8.31 + * @test 8.32 + * @run 8.33 + */ 8.34 + 8.35 +function createApplier(f) { 8.36 + function applier() { 8.37 + f.apply(this, arguments); // no transform applied here 8.38 + } 8.39 + return applier; 8.40 +} 8.41 + 8.42 +function printer(x,y) { 8.43 + print(x + " and " + y); 8.44 +} 8.45 + 8.46 +var printerApplier = createApplier(printer); 8.47 +printerApplier(); 8.48 +printerApplier.apply(this, ["foo", "bar"]);
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 9.2 +++ b/test/script/basic/JDK-8057825.js.EXPECTED Wed Nov 05 12:34:06 2014 +0100 9.3 @@ -0,0 +1,2 @@ 9.4 +undefined and undefined 9.5 +foo and bar
10.1 --- a/test/src/jdk/internal/dynalink/beans/CallerSensitiveTest.java Mon Nov 03 14:59:34 2014 +0100 10.2 +++ b/test/src/jdk/internal/dynalink/beans/CallerSensitiveTest.java Wed Nov 05 12:34:06 2014 +0100 10.3 @@ -28,6 +28,7 @@ 10.4 import jdk.nashorn.test.models.ClassLoaderAware; 10.5 import org.testng.annotations.Test; 10.6 10.7 +@SuppressWarnings("javadoc") 10.8 public class CallerSensitiveTest { 10.9 @Test 10.10 public void testCallerSensitive() {
11.1 --- a/test/src/jdk/nashorn/test/models/ClassLoaderAware.java Mon Nov 03 14:59:34 2014 +0100 11.2 +++ b/test/src/jdk/nashorn/test/models/ClassLoaderAware.java Wed Nov 05 12:34:06 2014 +0100 11.3 @@ -25,6 +25,7 @@ 11.4 11.5 package jdk.nashorn.test.models; 11.6 11.7 +@SuppressWarnings("javadoc") 11.8 public interface ClassLoaderAware { 11.9 public ClassLoader getContextClassLoader(); 11.10 public void checkMemberAccess(Class<?> clazz, int which);