Mon, 20 Oct 2014 12:06:36 +0200
8059844: Implement optimistic splitter
Reviewed-by: hannesw, lagergren
attila@963 | 1 | /* |
attila@963 | 2 | * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. |
attila@963 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
attila@963 | 4 | * |
attila@963 | 5 | * This code is free software; you can redistribute it and/or modify it |
attila@963 | 6 | * under the terms of the GNU General Public License version 2 only, as |
attila@963 | 7 | * published by the Free Software Foundation. Oracle designates this |
attila@963 | 8 | * particular file as subject to the "Classpath" exception as provided |
attila@963 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
attila@963 | 10 | * |
attila@963 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
attila@963 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
attila@963 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
attila@963 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
attila@963 | 15 | * accompanied this code). |
attila@963 | 16 | * |
attila@963 | 17 | * You should have received a copy of the GNU General Public License version |
attila@963 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
attila@963 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
attila@963 | 20 | * |
attila@963 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
attila@963 | 22 | * or visit www.oracle.com if you need additional information or have any |
attila@963 | 23 | * questions. |
attila@963 | 24 | */ |
attila@963 | 25 | |
attila@963 | 26 | package jdk.nashorn.internal.codegen; |
attila@963 | 27 | |
attila@963 | 28 | import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR; |
attila@963 | 29 | import static jdk.nashorn.internal.codegen.CompilerConstants.EXPLODED_ARGUMENT_PREFIX; |
attila@1064 | 30 | |
attila@963 | 31 | import java.lang.invoke.MethodType; |
attila@963 | 32 | import java.util.ArrayDeque; |
attila@963 | 33 | import java.util.ArrayList; |
attila@963 | 34 | import java.util.Deque; |
attila@963 | 35 | import java.util.HashSet; |
attila@963 | 36 | import java.util.List; |
attila@963 | 37 | import java.util.Set; |
attila@963 | 38 | import jdk.nashorn.internal.ir.AccessNode; |
attila@963 | 39 | import jdk.nashorn.internal.ir.CallNode; |
attila@963 | 40 | import jdk.nashorn.internal.ir.Expression; |
attila@963 | 41 | import jdk.nashorn.internal.ir.FunctionNode; |
attila@1064 | 42 | import jdk.nashorn.internal.ir.FunctionNode.CompilationState; |
attila@963 | 43 | import jdk.nashorn.internal.ir.IdentNode; |
attila@963 | 44 | import jdk.nashorn.internal.ir.LexicalContext; |
attila@963 | 45 | import jdk.nashorn.internal.ir.Node; |
attila@963 | 46 | import jdk.nashorn.internal.ir.visitor.NodeVisitor; |
attila@963 | 47 | import jdk.nashorn.internal.objects.Global; |
attila@963 | 48 | import jdk.nashorn.internal.runtime.Context; |
attila@963 | 49 | import jdk.nashorn.internal.runtime.logging.DebugLogger; |
attila@963 | 50 | import jdk.nashorn.internal.runtime.logging.Loggable; |
attila@963 | 51 | import jdk.nashorn.internal.runtime.logging.Logger; |
attila@963 | 52 | import jdk.nashorn.internal.runtime.options.Options; |
attila@963 | 53 | |
attila@963 | 54 | /** |
attila@963 | 55 | * An optimization that attempts to turn applies into calls. This pattern |
attila@963 | 56 | * is very common for fake class instance creation, and apply |
attila@963 | 57 | * introduces expensive args collection and boxing |
attila@963 | 58 | * |
attila@963 | 59 | * <pre> |
attila@963 | 60 | * var Class = { |
attila@963 | 61 | * create: function() { |
attila@963 | 62 | * return function() { //vararg |
attila@963 | 63 | * this.initialize.apply(this, arguments); |
attila@963 | 64 | * } |
attila@963 | 65 | * } |
attila@963 | 66 | * }; |
attila@963 | 67 | * |
attila@963 | 68 | * Color = Class.create(); |
attila@963 | 69 | * |
attila@963 | 70 | * Color.prototype = { |
attila@963 | 71 | * red: 0, green: 0, blue: 0, |
attila@963 | 72 | * initialize: function(r,g,b) { |
attila@963 | 73 | * this.red = r; |
attila@963 | 74 | * this.green = g; |
attila@963 | 75 | * this.blue = b; |
attila@963 | 76 | * } |
attila@963 | 77 | * } |
attila@963 | 78 | * |
attila@963 | 79 | * new Color(17, 47, 11); |
attila@963 | 80 | * </pre> |
attila@963 | 81 | */ |
attila@963 | 82 | |
attila@963 | 83 | @Logger(name="apply2call") |
attila@963 | 84 | public final class ApplySpecialization extends NodeVisitor<LexicalContext> implements Loggable { |
attila@963 | 85 | |
attila@963 | 86 | private static final boolean USE_APPLY2CALL = Options.getBooleanProperty("nashorn.apply2call", true); |
attila@963 | 87 | |
attila@963 | 88 | private final DebugLogger log; |
attila@963 | 89 | |
attila@963 | 90 | private final Compiler compiler; |
attila@963 | 91 | |
attila@963 | 92 | private final Set<Integer> changed = new HashSet<>(); |
attila@963 | 93 | |
attila@963 | 94 | private final Deque<List<IdentNode>> explodedArguments = new ArrayDeque<>(); |
attila@963 | 95 | |
attila@963 | 96 | private static final String ARGUMENTS = ARGUMENTS_VAR.symbolName(); |
attila@963 | 97 | |
attila@963 | 98 | /** |
attila@963 | 99 | * Apply specialization optimization. Try to explode arguments and call |
attila@963 | 100 | * applies as calls if they just pass on the "arguments" array and |
attila@963 | 101 | * "arguments" doesn't escape. |
attila@963 | 102 | * |
attila@963 | 103 | * @param compiler compiler |
attila@963 | 104 | */ |
attila@963 | 105 | public ApplySpecialization(final Compiler compiler) { |
attila@963 | 106 | super(new LexicalContext()); |
attila@963 | 107 | this.compiler = compiler; |
attila@963 | 108 | this.log = initLogger(compiler.getContext()); |
attila@963 | 109 | } |
attila@963 | 110 | |
attila@963 | 111 | @Override |
attila@963 | 112 | public DebugLogger getLogger() { |
attila@963 | 113 | return log; |
attila@963 | 114 | } |
attila@963 | 115 | |
attila@963 | 116 | @Override |
attila@963 | 117 | public DebugLogger initLogger(final Context context) { |
attila@963 | 118 | return context.getLogger(this.getClass()); |
attila@963 | 119 | } |
attila@963 | 120 | |
attila@963 | 121 | /** |
attila@963 | 122 | * Arguments may only be used as args to the apply. Everything else is disqualified |
attila@963 | 123 | * We cannot control arguments if they escape from the method and go into an unknown |
attila@963 | 124 | * scope, thus we are conservative and treat any access to arguments outside the |
attila@963 | 125 | * apply call as a case of "we cannot apply the optimization". |
attila@963 | 126 | * |
attila@963 | 127 | * @return true if arguments escape |
attila@963 | 128 | */ |
attila@963 | 129 | private boolean argumentsEscape(final FunctionNode functionNode) { |
attila@963 | 130 | |
lagergren@983 | 131 | @SuppressWarnings("serial") |
lagergren@983 | 132 | final UnsupportedOperationException uoe = new UnsupportedOperationException() { |
lagergren@983 | 133 | @Override |
lagergren@1028 | 134 | public synchronized Throwable fillInStackTrace() { |
lagergren@983 | 135 | return null; |
lagergren@983 | 136 | } |
lagergren@983 | 137 | }; |
lagergren@983 | 138 | |
lagergren@1028 | 139 | final Set<Expression> argumentsFound = new HashSet<>(); |
attila@963 | 140 | final Deque<Set<Expression>> stack = new ArrayDeque<>(); |
attila@963 | 141 | //ensure that arguments is only passed as arg to apply |
attila@963 | 142 | try { |
attila@963 | 143 | functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
attila@963 | 144 | private boolean isCurrentArg(final Expression expr) { |
attila@963 | 145 | return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call |
attila@963 | 146 | } |
attila@963 | 147 | |
attila@963 | 148 | private boolean isArguments(final Expression expr) { |
lagergren@1028 | 149 | if (expr instanceof IdentNode && ARGUMENTS.equals(((IdentNode)expr).getName())) { |
lagergren@1028 | 150 | argumentsFound.add(expr); |
lagergren@1028 | 151 | return true; |
lagergren@1028 | 152 | } |
lagergren@1028 | 153 | return false; |
attila@963 | 154 | } |
attila@963 | 155 | |
attila@963 | 156 | private boolean isParam(final String name) { |
attila@963 | 157 | for (final IdentNode param : functionNode.getParameters()) { |
attila@963 | 158 | if (param.getName().equals(name)) { |
attila@963 | 159 | return true; |
attila@963 | 160 | } |
attila@963 | 161 | } |
attila@963 | 162 | return false; |
attila@963 | 163 | } |
attila@963 | 164 | |
attila@963 | 165 | @Override |
attila@963 | 166 | public Node leaveIdentNode(final IdentNode identNode) { |
lagergren@1028 | 167 | if (isParam(identNode.getName()) || isArguments(identNode) && !isCurrentArg(identNode)) { |
lagergren@983 | 168 | throw uoe; //avoid filling in stack trace |
attila@963 | 169 | } |
attila@963 | 170 | return identNode; |
attila@963 | 171 | } |
attila@963 | 172 | |
attila@963 | 173 | @Override |
attila@963 | 174 | public boolean enterCallNode(final CallNode callNode) { |
attila@963 | 175 | final Set<Expression> callArgs = new HashSet<>(); |
attila@963 | 176 | if (isApply(callNode)) { |
attila@963 | 177 | final List<Expression> argList = callNode.getArgs(); |
attila@963 | 178 | if (argList.size() != 2 || !isArguments(argList.get(argList.size() - 1))) { |
attila@963 | 179 | throw new UnsupportedOperationException(); |
attila@963 | 180 | } |
attila@963 | 181 | callArgs.addAll(callNode.getArgs()); |
attila@963 | 182 | } |
attila@963 | 183 | stack.push(callArgs); |
attila@963 | 184 | return true; |
attila@963 | 185 | } |
attila@963 | 186 | |
attila@963 | 187 | @Override |
attila@963 | 188 | public Node leaveCallNode(final CallNode callNode) { |
attila@963 | 189 | stack.pop(); |
attila@963 | 190 | return callNode; |
attila@963 | 191 | } |
attila@963 | 192 | }); |
attila@963 | 193 | } catch (final UnsupportedOperationException e) { |
lagergren@1028 | 194 | if (!argumentsFound.isEmpty()) { |
lagergren@1028 | 195 | log.fine("'arguments' is used but escapes, or is reassigned in '" + functionNode.getName() + "'. Aborting"); |
lagergren@1028 | 196 | } |
attila@963 | 197 | return true; //bad |
attila@963 | 198 | } |
attila@963 | 199 | |
attila@963 | 200 | return false; |
attila@963 | 201 | } |
attila@963 | 202 | |
attila@963 | 203 | @Override |
attila@963 | 204 | public boolean enterCallNode(final CallNode callNode) { |
attila@963 | 205 | return !explodedArguments.isEmpty(); |
attila@963 | 206 | } |
attila@963 | 207 | |
attila@963 | 208 | @Override |
attila@963 | 209 | public Node leaveCallNode(final CallNode callNode) { |
attila@963 | 210 | //apply needs to be a global symbol or we don't allow it |
attila@963 | 211 | |
attila@963 | 212 | final List<IdentNode> newParams = explodedArguments.peek(); |
attila@963 | 213 | if (isApply(callNode)) { |
attila@963 | 214 | final List<Expression> newArgs = new ArrayList<>(); |
attila@963 | 215 | for (final Expression arg : callNode.getArgs()) { |
attila@963 | 216 | if (arg instanceof IdentNode && ARGUMENTS.equals(((IdentNode)arg).getName())) { |
attila@963 | 217 | newArgs.addAll(newParams); |
attila@963 | 218 | } else { |
attila@963 | 219 | newArgs.add(arg); |
attila@963 | 220 | } |
attila@963 | 221 | } |
attila@963 | 222 | |
attila@963 | 223 | changed.add(lc.getCurrentFunction().getId()); |
attila@963 | 224 | |
attila@963 | 225 | final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); |
attila@963 | 226 | |
attila@963 | 227 | log.fine("Transformed ", |
attila@963 | 228 | callNode, |
attila@963 | 229 | " from apply to call => ", |
attila@963 | 230 | newCallNode, |
attila@963 | 231 | " in ", |
attila@963 | 232 | DebugLogger.quote(lc.getCurrentFunction().getName())); |
attila@963 | 233 | |
attila@963 | 234 | return newCallNode; |
attila@963 | 235 | } |
attila@963 | 236 | |
attila@963 | 237 | return callNode; |
attila@963 | 238 | } |
attila@963 | 239 | |
attila@963 | 240 | private boolean pushExplodedArgs(final FunctionNode functionNode) { |
attila@963 | 241 | int start = 0; |
attila@963 | 242 | |
attila@963 | 243 | final MethodType actualCallSiteType = compiler.getCallSiteType(functionNode); |
attila@963 | 244 | if (actualCallSiteType == null) { |
attila@963 | 245 | return false; |
attila@963 | 246 | } |
attila@963 | 247 | assert actualCallSiteType.parameterType(actualCallSiteType.parameterCount() - 1) != Object[].class : "error vararg callsite passed to apply2call " + functionNode.getName() + " " + actualCallSiteType; |
attila@963 | 248 | |
attila@963 | 249 | final TypeMap ptm = compiler.getTypeMap(); |
attila@963 | 250 | if (ptm.needsCallee()) { |
attila@963 | 251 | start++; |
attila@963 | 252 | } |
attila@963 | 253 | |
attila@963 | 254 | start++; //we always uses this |
attila@963 | 255 | |
attila@963 | 256 | final List<IdentNode> params = functionNode.getParameters(); |
attila@963 | 257 | final List<IdentNode> newParams = new ArrayList<>(); |
attila@963 | 258 | final long to = Math.max(params.size(), actualCallSiteType.parameterCount() - start); |
attila@963 | 259 | for (int i = 0; i < to; i++) { |
attila@963 | 260 | if (i >= params.size()) { |
attila@963 | 261 | newParams.add(new IdentNode(functionNode.getToken(), functionNode.getFinish(), EXPLODED_ARGUMENT_PREFIX.symbolName() + (i))); |
attila@963 | 262 | } else { |
attila@963 | 263 | newParams.add(params.get(i)); |
attila@963 | 264 | } |
attila@963 | 265 | } |
attila@963 | 266 | |
attila@963 | 267 | explodedArguments.push(newParams); |
attila@963 | 268 | return true; |
attila@963 | 269 | } |
attila@963 | 270 | |
attila@963 | 271 | @Override |
attila@963 | 272 | public boolean enterFunctionNode(final FunctionNode functionNode) { |
attila@963 | 273 | if (!USE_APPLY2CALL) { |
attila@963 | 274 | return false; |
attila@963 | 275 | } |
attila@963 | 276 | |
lagergren@1028 | 277 | if (!Global.isBuiltinFunctionPrototypeApply()) { |
attila@963 | 278 | log.fine("Apply transform disabled: apply/call overridden"); |
lagergren@1028 | 279 | assert !Global.isBuiltinFunctionPrototypeCall() : "call and apply should have the same SwitchPoint"; |
attila@963 | 280 | return false; |
attila@963 | 281 | } |
attila@963 | 282 | |
attila@963 | 283 | if (!compiler.isOnDemandCompilation()) { |
attila@963 | 284 | return false; |
attila@963 | 285 | } |
attila@963 | 286 | |
attila@963 | 287 | if (functionNode.hasEval()) { |
attila@963 | 288 | return false; |
attila@963 | 289 | } |
attila@963 | 290 | |
attila@963 | 291 | if (argumentsEscape(functionNode)) { |
attila@963 | 292 | return false; |
attila@963 | 293 | } |
attila@963 | 294 | |
attila@963 | 295 | return pushExplodedArgs(functionNode); |
attila@963 | 296 | } |
attila@963 | 297 | |
attila@963 | 298 | /** |
attila@963 | 299 | * Try to do the apply to call transformation |
attila@963 | 300 | * @return true if successful, false otherwise |
attila@963 | 301 | */ |
attila@963 | 302 | @Override |
attila@963 | 303 | public Node leaveFunctionNode(final FunctionNode functionNode0) { |
attila@963 | 304 | FunctionNode newFunctionNode = functionNode0; |
attila@963 | 305 | final String functionName = newFunctionNode.getName(); |
attila@963 | 306 | |
attila@963 | 307 | if (changed.contains(newFunctionNode.getId())) { |
attila@963 | 308 | newFunctionNode = newFunctionNode.clearFlag(lc, FunctionNode.USES_ARGUMENTS). |
attila@963 | 309 | setFlag(lc, FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION). |
attila@963 | 310 | setParameters(lc, explodedArguments.peek()); |
attila@963 | 311 | |
attila@963 | 312 | if (log.isEnabled()) { |
attila@963 | 313 | log.info("Successfully specialized apply to call in '", |
attila@963 | 314 | functionName, |
attila@963 | 315 | " params=", |
attila@963 | 316 | explodedArguments.peek(), |
attila@963 | 317 | "' id=", |
attila@963 | 318 | newFunctionNode.getId(), |
attila@963 | 319 | " source=", |
attila@963 | 320 | newFunctionNode.getSource().getURL()); |
attila@963 | 321 | } |
attila@963 | 322 | } |
attila@963 | 323 | |
attila@963 | 324 | explodedArguments.pop(); |
attila@963 | 325 | |
attila@1064 | 326 | return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED); |
attila@963 | 327 | } |
attila@963 | 328 | |
attila@963 | 329 | private static boolean isApply(final CallNode callNode) { |
attila@963 | 330 | final Expression f = callNode.getFunction(); |
attila@963 | 331 | return f instanceof AccessNode && "apply".equals(((AccessNode)f).getProperty()); |
attila@963 | 332 | } |
attila@963 | 333 | |
attila@963 | 334 | } |