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 26 package jdk.nashorn.internal.codegen; 27 28 import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.EXPLODED_ARGUMENT_PREFIX; 30 31 import java.lang.invoke.MethodType; 32 import java.net.URL; 33 import java.util.ArrayDeque; 34 import java.util.ArrayList; 35 import java.util.Deque; 36 import java.util.HashSet; 37 import java.util.List; 38 import java.util.Set; 39 import jdk.nashorn.internal.ir.AccessNode; 40 import jdk.nashorn.internal.ir.CallNode; 41 import jdk.nashorn.internal.ir.Expression; 42 import jdk.nashorn.internal.ir.FunctionNode; 43 import jdk.nashorn.internal.ir.FunctionNode.CompilationState; 44 import jdk.nashorn.internal.ir.IdentNode; 45 import jdk.nashorn.internal.ir.LexicalContext; 46 import jdk.nashorn.internal.ir.Node; 47 import jdk.nashorn.internal.ir.visitor.NodeVisitor; 48 import jdk.nashorn.internal.objects.Global; 49 import jdk.nashorn.internal.runtime.Context; 50 import jdk.nashorn.internal.runtime.logging.DebugLogger; 51 import jdk.nashorn.internal.runtime.logging.Loggable; 52 import jdk.nashorn.internal.runtime.logging.Logger; 53 import 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") 85 public 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 enterFunctionNode(final FunctionNode fn) { 145 return fn == functionNode; 146 } 147 148 @Override 149 public boolean enterCallNode(final CallNode callNode) { 150 if (isApply(callNode)) { 151 throw HAS_APPLIES; 152 } 153 return true; 154 } 155 }); 156 } catch (final AppliesFoundException e) { 157 return true; 158 } 159 160 log.fine("There are no applies in ", DebugLogger.quote(functionNode.getName()), " - nothing to do."); 161 return false; // no applies 162 } 163 164 /** 165 * Arguments may only be used as args to the apply. Everything else is disqualified 166 * We cannot control arguments if they escape from the method and go into an unknown 167 * scope, thus we are conservative and treat any access to arguments outside the 168 * apply call as a case of "we cannot apply the optimization". 169 */ 170 private static void checkValidTransform(final FunctionNode functionNode) { 171 172 final Set<Expression> argumentsFound = new HashSet<>(); 173 final Deque<Set<Expression>> stack = new ArrayDeque<>(); 174 175 //ensure that arguments is only passed as arg to apply 176 functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 177 178 private boolean isCurrentArg(final Expression expr) { 179 return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call 180 } 181 182 private boolean isArguments(final Expression expr) { 183 if (expr instanceof IdentNode && ARGUMENTS.equals(((IdentNode)expr).getName())) { 184 argumentsFound.add(expr); 185 return true; 186 } 187 return false; 188 } 189 190 private boolean isParam(final String name) { 191 for (final IdentNode param : functionNode.getParameters()) { 192 if (param.getName().equals(name)) { 193 return true; 194 } 195 } 196 return false; 197 } 198 199 @Override 200 public Node leaveIdentNode(final IdentNode identNode) { 201 if (isParam(identNode.getName())) { 202 throw new TransformFailedException(lc.getCurrentFunction(), "parameter: " + identNode.getName()); 203 } 204 // it's OK if 'argument' occurs as the current argument of an apply 205 if (isArguments(identNode) && !isCurrentArg(identNode)) { 206 throw new TransformFailedException(lc.getCurrentFunction(), "is 'arguments': " + identNode.getName()); 207 } 208 return identNode; 209 } 210 211 @Override 212 public boolean enterCallNode(final CallNode callNode) { 213 final Set<Expression> callArgs = new HashSet<>(); 214 if (isApply(callNode)) { 215 final List<Expression> argList = callNode.getArgs(); 216 if (argList.size() != 2 || !isArguments(argList.get(argList.size() - 1))) { 217 throw new TransformFailedException(lc.getCurrentFunction(), "argument pattern not matched: " + argList); 218 } 219 callArgs.addAll(callNode.getArgs()); 220 } 221 stack.push(callArgs); 222 return true; 223 } 224 225 @Override 226 public Node leaveCallNode(final CallNode callNode) { 227 stack.pop(); 228 return callNode; 229 } 230 }); 231 } 232 233 @Override 234 public boolean enterCallNode(final CallNode callNode) { 235 return !explodedArguments.isEmpty(); 236 } 237 238 @Override 239 public Node leaveCallNode(final CallNode callNode) { 240 //apply needs to be a global symbol or we don't allow it 241 242 final List<IdentNode> newParams = explodedArguments.peek(); 243 if (isApply(callNode)) { 244 final List<Expression> newArgs = new ArrayList<>(); 245 for (final Expression arg : callNode.getArgs()) { 246 if (arg instanceof IdentNode && ARGUMENTS.equals(((IdentNode)arg).getName())) { 247 newArgs.addAll(newParams); 248 } else { 249 newArgs.add(arg); 250 } 251 } 252 253 changed.add(lc.getCurrentFunction().getId()); 254 255 final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); 256 257 if (log.isEnabled()) { 258 log.fine("Transformed ", 259 callNode, 260 " from apply to call => ", 261 newCallNode, 262 " in ", 263 DebugLogger.quote(lc.getCurrentFunction().getName())); 264 } 265 266 return newCallNode; 267 } 268 269 return callNode; 270 } 271 272 private void pushExplodedArgs(final FunctionNode functionNode) { 273 int start = 0; 274 275 final MethodType actualCallSiteType = compiler.getCallSiteType(functionNode); 276 if (actualCallSiteType == null) { 277 throw new TransformFailedException(lc.getCurrentFunction(), "No callsite type"); 278 } 279 assert actualCallSiteType.parameterType(actualCallSiteType.parameterCount() - 1) != Object[].class : "error vararg callsite passed to apply2call " + functionNode.getName() + " " + actualCallSiteType; 280 281 final TypeMap ptm = compiler.getTypeMap(); 282 if (ptm.needsCallee()) { 283 start++; 284 } 285 286 start++; // we always use this 287 288 assert functionNode.getNumOfParams() == 0 : "apply2call on function with named paramaters!"; 289 final List<IdentNode> newParams = new ArrayList<>(); 290 final long to = actualCallSiteType.parameterCount() - start; 291 for (int i = 0; i < to; i++) { 292 newParams.add(new IdentNode(functionNode.getToken(), functionNode.getFinish(), EXPLODED_ARGUMENT_PREFIX.symbolName() + (i))); 293 } 294 295 callSiteTypes.push(actualCallSiteType); 296 explodedArguments.push(newParams); 297 } 298 299 @Override 300 public boolean enterFunctionNode(final FunctionNode functionNode) { 301 if (!USE_APPLY2CALL) { 302 return false; 303 } 304 305 if (!Global.isBuiltinFunctionPrototypeApply()) { 306 log.fine("Apply transform disabled: apply/call overridden"); 307 assert !Global.isBuiltinFunctionPrototypeCall() : "call and apply should have the same SwitchPoint"; 308 return false; 309 } 310 311 if (!compiler.isOnDemandCompilation()) { 312 return false; 313 } 314 315 if (functionNode.getNumOfParams() != 0) { 316 return false; 317 } 318 319 if (functionNode.hasEval()) { 320 return false; 321 } 322 323 if (!hasApplies(functionNode)) { 324 return false; 325 } 326 327 if (log.isEnabled()) { 328 log.info("Trying to specialize apply to call in '", 329 functionNode.getName(), 330 "' params=", 331 functionNode.getParameters(), 332 " id=", 333 functionNode.getId(), 334 " source=", 335 massageURL(functionNode.getSource().getURL())); 336 } 337 338 try { 339 checkValidTransform(functionNode); 340 pushExplodedArgs(functionNode); 341 } catch (final TransformFailedException e) { 342 log.info("Failure: ", e.getMessage()); 343 return false; 344 } 345 346 return true; 347 } 348 349 /** 350 * Try to do the apply to call transformation 351 * @return true if successful, false otherwise 352 */ 353 @Override 354 public Node leaveFunctionNode(final FunctionNode functionNode) { 355 FunctionNode newFunctionNode = functionNode; 356 final String functionName = newFunctionNode.getName(); 357 358 if (changed.contains(newFunctionNode.getId())) { 359 newFunctionNode = newFunctionNode.clearFlag(lc, FunctionNode.USES_ARGUMENTS). 360 setFlag(lc, FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION). 361 setParameters(lc, explodedArguments.peek()); 362 363 if (log.isEnabled()) { 364 log.info("Success: ", 365 massageURL(newFunctionNode.getSource().getURL()), 366 '.', 367 functionName, 368 "' id=", 369 newFunctionNode.getId(), 370 " params=", 371 callSiteTypes.peek()); 372 } 373 } 374 375 callSiteTypes.pop(); 376 explodedArguments.pop(); 377 378 return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED); 379 } 380 381 private static boolean isApply(final CallNode callNode) { 382 final Expression f = callNode.getFunction(); 383 return f instanceof AccessNode && "apply".equals(((AccessNode)f).getProperty()); 384 } 385 386 private static String massageURL(final URL url) { 387 if (url == null) { 388 return "<null>"; 389 } 390 final String str = url.toString(); 391 final int slash = str.lastIndexOf('/'); 392 if (slash == -1) { 393 return str; 394 } 395 return str.substring(slash + 1); 396 } 397 }