ApplySpecialization.java revision 1092:569b6de2d343
1/* 2 * Copyright (c) 2010, 2014, 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 */ 25 26package jdk.nashorn.internal.codegen; 27 28import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR; 29import static jdk.nashorn.internal.codegen.CompilerConstants.EXPLODED_ARGUMENT_PREFIX; 30 31import java.lang.invoke.MethodType; 32import java.net.URL; 33import java.util.ArrayDeque; 34import java.util.ArrayList; 35import java.util.Deque; 36import java.util.HashSet; 37import java.util.List; 38import java.util.Set; 39import jdk.nashorn.internal.ir.AccessNode; 40import jdk.nashorn.internal.ir.CallNode; 41import jdk.nashorn.internal.ir.Expression; 42import jdk.nashorn.internal.ir.FunctionNode; 43import jdk.nashorn.internal.ir.FunctionNode.CompilationState; 44import jdk.nashorn.internal.ir.IdentNode; 45import jdk.nashorn.internal.ir.LexicalContext; 46import jdk.nashorn.internal.ir.Node; 47import jdk.nashorn.internal.ir.visitor.NodeVisitor; 48import jdk.nashorn.internal.objects.Global; 49import jdk.nashorn.internal.runtime.Context; 50import jdk.nashorn.internal.runtime.logging.DebugLogger; 51import jdk.nashorn.internal.runtime.logging.Loggable; 52import jdk.nashorn.internal.runtime.logging.Logger; 53import jdk.nashorn.internal.runtime.options.Options; 54 55/** 56 * An optimization that attempts to turn applies into calls. This pattern 57 * is very common for fake class instance creation, and apply 58 * introduces expensive args collection and boxing 59 * 60 * <pre> 61 * var Class = { 62 * create: function() { 63 * return function() { //vararg 64 * this.initialize.apply(this, arguments); 65 * } 66 * } 67 * }; 68 * 69 * Color = Class.create(); 70 * 71 * Color.prototype = { 72 * red: 0, green: 0, blue: 0, 73 * initialize: function(r,g,b) { 74 * this.red = r; 75 * this.green = g; 76 * this.blue = b; 77 * } 78 * } 79 * 80 * new Color(17, 47, 11); 81 * </pre> 82 */ 83 84@Logger(name="apply2call") 85public final class ApplySpecialization extends NodeVisitor<LexicalContext> implements Loggable { 86 87 private static final boolean USE_APPLY2CALL = Options.getBooleanProperty("nashorn.apply2call", true); 88 89 private final DebugLogger log; 90 91 private final Compiler compiler; 92 93 private final Set<Integer> changed = new HashSet<>(); 94 95 private final Deque<List<IdentNode>> explodedArguments = new ArrayDeque<>(); 96 97 private final Deque<MethodType> callSiteTypes = new ArrayDeque<>(); 98 99 private static final String ARGUMENTS = ARGUMENTS_VAR.symbolName(); 100 101 /** 102 * Apply specialization optimization. Try to explode arguments and call 103 * applies as calls if they just pass on the "arguments" array and 104 * "arguments" doesn't escape. 105 * 106 * @param compiler compiler 107 */ 108 public ApplySpecialization(final Compiler compiler) { 109 super(new LexicalContext()); 110 this.compiler = compiler; 111 this.log = initLogger(compiler.getContext()); 112 } 113 114 @Override 115 public DebugLogger getLogger() { 116 return log; 117 } 118 119 @Override 120 public DebugLogger initLogger(final Context context) { 121 return context.getLogger(this.getClass()); 122 } 123 124 @SuppressWarnings("serial") 125 private static class TransformFailedException extends RuntimeException { 126 TransformFailedException(final FunctionNode fn, final String message) { 127 super(massageURL(fn.getSource().getURL()) + '.' + fn.getName() + " => " + message, null, false, false); 128 } 129 } 130 131 @SuppressWarnings("serial") 132 private static class AppliesFoundException extends RuntimeException { 133 AppliesFoundException() { 134 super("applies_found", null, false, false); 135 } 136 } 137 138 private static final AppliesFoundException HAS_APPLIES = new AppliesFoundException(); 139 140 private boolean hasApplies(final FunctionNode functionNode) { 141 try { 142 functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 143 @Override 144 public boolean enterCallNode(final CallNode callNode) { 145 if (isApply(callNode)) { 146 throw HAS_APPLIES; 147 } 148 return true; 149 } 150 }); 151 } catch (final AppliesFoundException e) { 152 return true; 153 } 154 155 log.fine("There are no applies in ", DebugLogger.quote(functionNode.getName()), " - nothing to do."); 156 return false; // no applies 157 } 158 159 /** 160 * Arguments may only be used as args to the apply. Everything else is disqualified 161 * We cannot control arguments if they escape from the method and go into an unknown 162 * scope, thus we are conservative and treat any access to arguments outside the 163 * apply call as a case of "we cannot apply the optimization". 164 */ 165 private void checkValidTransform(final FunctionNode functionNode) { 166 167 final Set<Expression> argumentsFound = new HashSet<>(); 168 final Deque<Set<Expression>> stack = new ArrayDeque<>(); 169 170 //ensure that arguments is only passed as arg to apply 171 functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 172 173 private boolean isCurrentArg(final Expression expr) { 174 return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call 175 } 176 177 private boolean isArguments(final Expression expr) { 178 if (expr instanceof IdentNode && ARGUMENTS.equals(((IdentNode)expr).getName())) { 179 argumentsFound.add(expr); 180 return true; 181 } 182 return false; 183 } 184 185 private boolean isParam(final String name) { 186 for (final IdentNode param : functionNode.getParameters()) { 187 if (param.getName().equals(name)) { 188 return true; 189 } 190 } 191 return false; 192 } 193 194 @Override 195 public Node leaveIdentNode(final IdentNode identNode) { 196 if (isParam(identNode.getName())) { 197 throw new TransformFailedException(lc.getCurrentFunction(), "parameter: " + identNode.getName()); 198 } 199 // it's OK if 'argument' occurs as the current argument of an apply 200 if (isArguments(identNode) && !isCurrentArg(identNode)) { 201 throw new TransformFailedException(lc.getCurrentFunction(), "is 'arguments': " + identNode.getName()); 202 } 203 return identNode; 204 } 205 206 @Override 207 public boolean enterCallNode(final CallNode callNode) { 208 final Set<Expression> callArgs = new HashSet<>(); 209 if (isApply(callNode)) { 210 final List<Expression> argList = callNode.getArgs(); 211 if (argList.size() != 2 || !isArguments(argList.get(argList.size() - 1))) { 212 throw new TransformFailedException(lc.getCurrentFunction(), "argument pattern not matched: " + argList); 213 } 214 callArgs.addAll(callNode.getArgs()); 215 } 216 stack.push(callArgs); 217 return true; 218 } 219 220 @Override 221 public Node leaveCallNode(final CallNode callNode) { 222 stack.pop(); 223 return callNode; 224 } 225 }); 226 } 227 228 @Override 229 public boolean enterCallNode(final CallNode callNode) { 230 return !explodedArguments.isEmpty(); 231 } 232 233 @Override 234 public Node leaveCallNode(final CallNode callNode) { 235 //apply needs to be a global symbol or we don't allow it 236 237 final List<IdentNode> newParams = explodedArguments.peek(); 238 if (isApply(callNode)) { 239 final List<Expression> newArgs = new ArrayList<>(); 240 for (final Expression arg : callNode.getArgs()) { 241 if (arg instanceof IdentNode && ARGUMENTS.equals(((IdentNode)arg).getName())) { 242 newArgs.addAll(newParams); 243 } else { 244 newArgs.add(arg); 245 } 246 } 247 248 changed.add(lc.getCurrentFunction().getId()); 249 250 final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); 251 252 if (log.isEnabled()) { 253 log.fine("Transformed ", 254 callNode, 255 " from apply to call => ", 256 newCallNode, 257 " in ", 258 DebugLogger.quote(lc.getCurrentFunction().getName())); 259 } 260 261 return newCallNode; 262 } 263 264 return callNode; 265 } 266 267 private void pushExplodedArgs(final FunctionNode functionNode) { 268 int start = 0; 269 270 final MethodType actualCallSiteType = compiler.getCallSiteType(functionNode); 271 if (actualCallSiteType == null) { 272 throw new TransformFailedException(lc.getCurrentFunction(), "No callsite type"); 273 } 274 assert actualCallSiteType.parameterType(actualCallSiteType.parameterCount() - 1) != Object[].class : "error vararg callsite passed to apply2call " + functionNode.getName() + " " + actualCallSiteType; 275 276 final TypeMap ptm = compiler.getTypeMap(); 277 if (ptm.needsCallee()) { 278 start++; 279 } 280 281 start++; //we always uses this 282 283 final List<IdentNode> params = functionNode.getParameters(); 284 final List<IdentNode> newParams = new ArrayList<>(); 285 final long to = Math.max(params.size(), actualCallSiteType.parameterCount() - start); 286 for (int i = 0; i < to; i++) { 287 if (i >= params.size()) { 288 newParams.add(new IdentNode(functionNode.getToken(), functionNode.getFinish(), EXPLODED_ARGUMENT_PREFIX.symbolName() + (i))); 289 } else { 290 newParams.add(params.get(i)); 291 } 292 } 293 294 callSiteTypes.push(actualCallSiteType); 295 explodedArguments.push(newParams); 296 } 297 298 @Override 299 public boolean enterFunctionNode(final FunctionNode functionNode) { 300 if (!USE_APPLY2CALL) { 301 return false; 302 } 303 304 if (!Global.isBuiltinFunctionPrototypeApply()) { 305 log.fine("Apply transform disabled: apply/call overridden"); 306 assert !Global.isBuiltinFunctionPrototypeCall() : "call and apply should have the same SwitchPoint"; 307 return false; 308 } 309 310 if (!compiler.isOnDemandCompilation()) { 311 return false; 312 } 313 314 if (functionNode.hasEval()) { 315 return false; 316 } 317 318 if (!hasApplies(functionNode)) { 319 return false; 320 } 321 322 if (log.isEnabled()) { 323 log.info("Trying to specialize apply to call in '", 324 functionNode.getName(), 325 "' params=", 326 functionNode.getParameters(), 327 " id=", 328 functionNode.getId(), 329 " source=", 330 massageURL(functionNode.getSource().getURL())); 331 } 332 333 try { 334 checkValidTransform(functionNode); 335 pushExplodedArgs(functionNode); 336 } catch (final TransformFailedException e) { 337 log.info("Failure: ", e.getMessage()); 338 return false; 339 } 340 341 return true; 342 } 343 344 /** 345 * Try to do the apply to call transformation 346 * @return true if successful, false otherwise 347 */ 348 @Override 349 public Node leaveFunctionNode(final FunctionNode functionNode) { 350 FunctionNode newFunctionNode = functionNode; 351 final String functionName = newFunctionNode.getName(); 352 353 if (changed.contains(newFunctionNode.getId())) { 354 newFunctionNode = newFunctionNode.clearFlag(lc, FunctionNode.USES_ARGUMENTS). 355 setFlag(lc, FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION). 356 setParameters(lc, explodedArguments.peek()); 357 358 if (log.isEnabled()) { 359 log.info("Success: ", 360 massageURL(newFunctionNode.getSource().getURL()), 361 '.', 362 functionName, 363 "' id=", 364 newFunctionNode.getId(), 365 " params=", 366 callSiteTypes.peek()); 367 } 368 } 369 370 callSiteTypes.pop(); 371 explodedArguments.pop(); 372 373 return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED); 374 } 375 376 private static boolean isApply(final CallNode callNode) { 377 final Expression f = callNode.getFunction(); 378 return f instanceof AccessNode && "apply".equals(((AccessNode)f).getProperty()); 379 } 380 381 private static String massageURL(final URL url) { 382 if (url == null) { 383 return "<null>"; 384 } 385 final String str = url.toString(); 386 final int slash = str.lastIndexOf('/'); 387 if (slash == -1) { 388 return str; 389 } 390 return str.substring(slash + 1); 391 } 392} 393