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