SplitIntoFunctions.java revision 1070:34d55faf0b3a
1/* 2 * Copyright (c) 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.ir.Node.NO_FINISH; 29import static jdk.nashorn.internal.ir.Node.NO_LINE_NUMBER; 30import static jdk.nashorn.internal.ir.Node.NO_TOKEN; 31 32import java.util.ArrayDeque; 33import java.util.ArrayList; 34import java.util.Arrays; 35import java.util.Collections; 36import java.util.Deque; 37import java.util.List; 38import java.util.Objects; 39import jdk.nashorn.internal.ir.AccessNode; 40import jdk.nashorn.internal.ir.BinaryNode; 41import jdk.nashorn.internal.ir.Block; 42import jdk.nashorn.internal.ir.BlockLexicalContext; 43import jdk.nashorn.internal.ir.BreakNode; 44import jdk.nashorn.internal.ir.CallNode; 45import jdk.nashorn.internal.ir.CaseNode; 46import jdk.nashorn.internal.ir.ContinueNode; 47import jdk.nashorn.internal.ir.Expression; 48import jdk.nashorn.internal.ir.ExpressionStatement; 49import jdk.nashorn.internal.ir.FunctionNode; 50import jdk.nashorn.internal.ir.FunctionNode.CompilationState; 51import jdk.nashorn.internal.ir.GetSplitState; 52import jdk.nashorn.internal.ir.IdentNode; 53import jdk.nashorn.internal.ir.IfNode; 54import jdk.nashorn.internal.ir.JumpStatement; 55import jdk.nashorn.internal.ir.LiteralNode; 56import jdk.nashorn.internal.ir.Node; 57import jdk.nashorn.internal.ir.ReturnNode; 58import jdk.nashorn.internal.ir.SetSplitState; 59import jdk.nashorn.internal.ir.SplitNode; 60import jdk.nashorn.internal.ir.SplitReturn; 61import jdk.nashorn.internal.ir.Statement; 62import jdk.nashorn.internal.ir.SwitchNode; 63import jdk.nashorn.internal.ir.VarNode; 64import jdk.nashorn.internal.ir.visitor.NodeVisitor; 65import jdk.nashorn.internal.parser.Token; 66import jdk.nashorn.internal.parser.TokenType; 67 68/** 69 * A node visitor that replaces {@link SplitNode}s with anonymous function invocations and some additional constructs 70 * to support control flow across splits. By using this transformation, split functions are translated into ordinary 71 * JavaScript functions with nested anonymous functions. The transformations however introduce several AST nodes that 72 * have no JavaScript source representations ({@link GetSplitState}, {@link SetSplitState}, and {@link SplitReturn}), 73 * and therefore such function is no longer reparseable from its source. For that reason, split functions and their 74 * fragments are serialized in-memory and deserialized when they need to be recompiled either for deoptimization or 75 * for type specialization. 76 * NOTE: all {@code leave*()} methods for statements are returning their input nodes. That way, they will not mutate 77 * the original statement list in the block containing the statement, which is fine, as it'll be replaced by the 78 * lexical context when the block is left. If we returned something else (e.g. null), we'd cause a mutation in the 79 * enclosing block's statement list that is otherwise overwritten later anyway. 80 */ 81final class SplitIntoFunctions extends NodeVisitor<BlockLexicalContext> { 82 private static final int FALLTHROUGH_STATE = -1; 83 private static final int RETURN_STATE = 0; 84 private static final int BREAK_STATE = 1; 85 private static final int FIRST_JUMP_STATE = 2; 86 87 private static final String THIS_NAME = CompilerConstants.THIS.symbolName(); 88 private static final String RETURN_NAME = CompilerConstants.RETURN.symbolName(); 89 // Used as the name of the formal parameter for passing the current value of :return symbol into a split fragment. 90 private static final String RETURN_PARAM_NAME = RETURN_NAME + "-in"; 91 92 private final Deque<FunctionState> functionStates = new ArrayDeque<>(); 93 private final Deque<SplitState> splitStates = new ArrayDeque<>(); 94 private final Namespace namespace; 95 96 private boolean artificialBlock = false; 97 98 // -1 is program; we need to use negative ones 99 private int nextFunctionId = -2; 100 101 public SplitIntoFunctions(final Compiler compiler) { 102 super(new BlockLexicalContext() { 103 @Override 104 protected Block afterSetStatements(Block block) { 105 for(Statement stmt: block.getStatements()) { 106 assert !(stmt instanceof SplitNode); 107 } 108 return block; 109 } 110 }); 111 namespace = new Namespace(compiler.getScriptEnvironment().getNamespace()); 112 } 113 114 @Override 115 public boolean enterFunctionNode(final FunctionNode functionNode) { 116 functionStates.push(new FunctionState(functionNode)); 117 return true; 118 } 119 120 @Override 121 public Node leaveFunctionNode(final FunctionNode functionNode) { 122 functionStates.pop(); 123 return functionNode; 124 } 125 126 @Override 127 protected Node leaveDefault(final Node node) { 128 if (node instanceof Statement) { 129 appendStatement((Statement)node); 130 } 131 return node; 132 } 133 134 @Override 135 public boolean enterSplitNode(final SplitNode splitNode) { 136 getCurrentFunctionState().splitDepth++; 137 splitStates.push(new SplitState(splitNode)); 138 return true; 139 } 140 141 @Override 142 public Node leaveSplitNode(final SplitNode splitNode) { 143 // Replace the split node with an anonymous function expression call. 144 145 final FunctionState fnState = getCurrentFunctionState(); 146 147 final String name = splitNode.getName(); 148 Block body = splitNode.getBody(); 149 final int firstLineNumber = body.getFirstStatementLineNumber(); 150 final long token = body.getToken(); 151 final int finish = body.getFinish(); 152 153 final FunctionNode originalFn = fnState.fn; 154 assert originalFn == lc.getCurrentFunction(); 155 final boolean isProgram = originalFn.isProgram(); 156 157 // Change SplitNode({...}) into "function () { ... }", or "function (:return-in) () { ... }" (for program) 158 final long newFnToken = Token.toDesc(TokenType.FUNCTION, nextFunctionId--, 0); 159 final FunctionNode fn = new FunctionNode( 160 originalFn.getSource(), 161 body.getFirstStatementLineNumber(), 162 newFnToken, 163 finish, 164 newFnToken, 165 NO_TOKEN, 166 namespace, 167 createIdent(name), 168 originalFn.getName() + "$" + name, 169 isProgram ? Collections.singletonList(createReturnParamIdent()) : Collections.<IdentNode>emptyList(), 170 FunctionNode.Kind.NORMAL, 171 // We only need IS_SPLIT conservatively, in case it contains any array units so that we force 172 // the :callee's existence, to force :scope to never be in a slot lower than 2. This is actually 173 // quite a horrible hack to do with CodeGenerator.fixScopeSlot not trampling other parameters 174 // and should go away once we no longer have array unit handling in codegen. Note however that 175 // we still use IS_SPLIT as the criteria in CompilationPhase.SERIALIZE_SPLIT_PHASE. 176 FunctionNode.IS_ANONYMOUS | FunctionNode.USES_ANCESTOR_SCOPE | FunctionNode.IS_SPLIT, 177 body, 178 CompilationState.INITIALIZED, 179 null 180 ) 181 .setCompileUnit(lc, splitNode.getCompileUnit()) 182 .copyCompilationState(lc, originalFn); 183 184 // Call the function: 185 // either "(function () { ... }).call(this)" 186 // or "(function (:return-in) { ... }).call(this, :return)" 187 // NOTE: Function.call() has optimized linking that basically does a pass-through to the function being invoked. 188 // NOTE: CompilationPhase.PROGRAM_POINT_PHASE happens after this, so these calls are subject to optimistic 189 // assumptions on their return value (when they return a value), as they should be. 190 final IdentNode thisIdent = createIdent(THIS_NAME); 191 final CallNode callNode = new CallNode(firstLineNumber, token, finish, new AccessNode(NO_TOKEN, NO_FINISH, fn, "call"), 192 isProgram ? Arrays.<Expression>asList(thisIdent, createReturnIdent()) 193 : Collections.<Expression>singletonList(thisIdent), 194 false); 195 196 final SplitState splitState = splitStates.pop(); 197 fnState.splitDepth--; 198 199 final Expression callWithReturn; 200 final boolean hasReturn = splitState.hasReturn; 201 if (hasReturn && fnState.splitDepth > 0) { 202 final SplitState parentSplit = splitStates.peek(); 203 if (parentSplit != null) { 204 // Propagate hasReturn to parent split 205 parentSplit.hasReturn = true; 206 } 207 } 208 if (hasReturn || isProgram) { 209 // capture return value: ":return = (function () { ... })();" 210 callWithReturn = new BinaryNode(Token.recast(token, TokenType.ASSIGN), createReturnIdent(), callNode); 211 } else { 212 // no return value, just call : "(function () { ... })();" 213 callWithReturn = callNode; 214 } 215 appendStatement(new ExpressionStatement(firstLineNumber, token, finish, callWithReturn)); 216 217 Statement splitStateHandler; 218 219 final List<JumpStatement> jumpStatements = splitState.jumpStatements; 220 final int jumpCount = jumpStatements.size(); 221 // There are jumps (breaks or continues) that need to be propagated outside the split node. We need to 222 // set up a switch statement for them: 223 // switch(:scope.getScopeState()) { ... } 224 if (jumpCount > 0) { 225 final List<CaseNode> cases = new ArrayList<>(jumpCount + (hasReturn ? 1 : 0)); 226 if (hasReturn) { 227 // If the split node also contained a return, we'll slip it as a case in the switch statement 228 addCase(cases, RETURN_STATE, createReturnFromSplit()); 229 } 230 int i = FIRST_JUMP_STATE; 231 for (final JumpStatement jump: jumpStatements) { 232 addCase(cases, i++, enblockAndVisit(jump)); 233 } 234 splitStateHandler = new SwitchNode(NO_LINE_NUMBER, token, finish, GetSplitState.INSTANCE, cases, null); 235 } else { 236 splitStateHandler = null; 237 } 238 239 // As the switch statement itself is breakable, an unlabelled break can't be in the switch statement, 240 // so we need to test for it separately. 241 if (splitState.hasBreak) { 242 // if(:scope.getScopeState() == Scope.BREAK) { break; } 243 splitStateHandler = makeIfStateEquals(firstLineNumber, token, finish, BREAK_STATE, 244 enblockAndVisit(new BreakNode(NO_LINE_NUMBER, token, finish, null)), splitStateHandler); 245 } 246 247 // Finally, if the split node had a return statement, but there were no external jumps, we didn't have 248 // the switch statement to handle the return, so we need a separate if for it. 249 if (hasReturn && jumpCount == 0) { 250 // if (:scope.getScopeState() == Scope.RETURN) { return :return; } 251 splitStateHandler = makeIfStateEquals(NO_LINE_NUMBER, token, finish, RETURN_STATE, 252 createReturnFromSplit(), splitStateHandler); 253 } 254 255 if (splitStateHandler != null) { 256 appendStatement(splitStateHandler); 257 } 258 259 return splitNode; 260 } 261 262 private static void addCase(final List<CaseNode> cases, final int i, final Block body) { 263 cases.add(new CaseNode(NO_TOKEN, NO_FINISH, intLiteral(i), body)); 264 } 265 266 private static LiteralNode<Number> intLiteral(final int i) { 267 return LiteralNode.newInstance(NO_TOKEN, NO_FINISH, i); 268 } 269 270 private static Block createReturnFromSplit() { 271 return new Block(NO_TOKEN, NO_FINISH, createReturnReturn()); 272 } 273 274 private static ReturnNode createReturnReturn() { 275 return new ReturnNode(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, createReturnIdent()); 276 } 277 278 private static IdentNode createReturnIdent() { 279 return createIdent(RETURN_NAME); 280 } 281 282 private static IdentNode createReturnParamIdent() { 283 return createIdent(RETURN_PARAM_NAME); 284 } 285 286 private static IdentNode createIdent(final String name) { 287 return new IdentNode(NO_TOKEN, NO_FINISH, name); 288 } 289 290 private Block enblockAndVisit(final JumpStatement jump) { 291 artificialBlock = true; 292 final Block block = (Block)new Block(NO_TOKEN, NO_FINISH, jump).accept(this); 293 artificialBlock = false; 294 return block; 295 } 296 297 private static IfNode makeIfStateEquals(final int lineNumber, final long token, final int finish, 298 final int value, final Block pass, final Statement fail) { 299 return new IfNode(lineNumber, token, finish, 300 new BinaryNode(Token.recast(token, TokenType.EQ_STRICT), 301 GetSplitState.INSTANCE, intLiteral(value)), 302 pass, 303 fail == null ? null : new Block(NO_TOKEN, NO_FINISH, fail)); 304 } 305 306 @Override 307 public boolean enterVarNode(VarNode varNode) { 308 if (!inSplitNode()) { 309 return super.enterVarNode(varNode); 310 } 311 assert !varNode.isBlockScoped(); //TODO: we must handle these too, but we currently don't 312 313 final Expression init = varNode.getInit(); 314 if (varNode.isAnonymousFunctionDeclaration()) { 315 // We ain't moving anonymous function declarations. 316 return super.enterVarNode(varNode); 317 } 318 319 // Move a declaration-only var statement to the top of the outermost function. 320 getCurrentFunctionState().varStatements.add(varNode.setInit(null)); 321 // If it had an initializer, replace it with an assignment expression statement. Note that "var" is a 322 // statement, so it doesn't contribute to :return of the programs, therefore we are _not_ adding a 323 // ":return = ..." assignment around the original assignment. 324 if (init != null) { 325 final long token = Token.recast(varNode.getToken(), TokenType.ASSIGN); 326 new ExpressionStatement(varNode.getLineNumber(), token, varNode.getFinish(), 327 new BinaryNode(token, varNode.getName(), varNode.getInit())).accept(this); 328 } 329 330 return false; 331 } 332 333 @Override 334 public Node leaveBlock(final Block block) { 335 if (!artificialBlock) { 336 if (lc.isFunctionBody()) { 337 // Prepend declaration-only var statements to the top of the statement list. 338 lc.prependStatements(getCurrentFunctionState().varStatements); 339 } else if (lc.isSplitBody()) { 340 appendSplitReturn(FALLTHROUGH_STATE, NO_LINE_NUMBER); 341 if (getCurrentFunctionState().fn.isProgram()) { 342 // If we're splitting the program, make sure every shard ends with "return :return" and 343 // begins with ":return = :return-in;". 344 lc.prependStatement(new ExpressionStatement(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, 345 new BinaryNode(Token.toDesc(TokenType.ASSIGN, 0, 0), createReturnIdent(), createReturnParamIdent()))); 346 } 347 } 348 } 349 return block; 350 } 351 352 @Override 353 public Node leaveBreakNode(final BreakNode breakNode) { 354 return leaveJumpNode(breakNode); 355 } 356 357 @Override 358 public Node leaveContinueNode(final ContinueNode continueNode) { 359 return leaveJumpNode(continueNode); 360 } 361 362 private JumpStatement leaveJumpNode(final JumpStatement jump) { 363 if (inSplitNode()) { 364 final SplitState splitState = getCurrentSplitState(); 365 final SplitNode splitNode = splitState.splitNode; 366 if (lc.isExternalTarget(splitNode, jump.getTarget(lc))) { 367 appendSplitReturn(splitState.getSplitStateIndex(jump), jump.getLineNumber()); 368 return jump; 369 } 370 } 371 appendStatement(jump); 372 return jump; 373 } 374 375 private void appendSplitReturn(final int splitState, final int lineNumber) { 376 appendStatement(new SetSplitState(splitState, lineNumber)); 377 if (getCurrentFunctionState().fn.isProgram()) { 378 // If we're splitting the program, make sure every fragment passes back :return 379 appendStatement(createReturnReturn()); 380 } else { 381 appendStatement(SplitReturn.INSTANCE); 382 } 383 } 384 385 @Override 386 public Node leaveReturnNode(final ReturnNode returnNode) { 387 if(inSplitNode()) { 388 appendStatement(new SetSplitState(RETURN_STATE, returnNode.getLineNumber())); 389 getCurrentSplitState().hasReturn = true; 390 } 391 appendStatement(returnNode); 392 return returnNode; 393 } 394 395 private void appendStatement(final Statement statement) { 396 lc.appendStatement(statement); 397 } 398 399 private boolean inSplitNode() { 400 return getCurrentFunctionState().splitDepth > 0; 401 } 402 403 private FunctionState getCurrentFunctionState() { 404 return functionStates.peek(); 405 } 406 407 private SplitState getCurrentSplitState() { 408 return splitStates.peek(); 409 } 410 411 private static class FunctionState { 412 final FunctionNode fn; 413 final List<Statement> varStatements = new ArrayList<>(); 414 int splitDepth; 415 416 FunctionState(final FunctionNode fn) { 417 this.fn = fn; 418 } 419 } 420 421 private static class SplitState { 422 final SplitNode splitNode; 423 boolean hasReturn; 424 boolean hasBreak; 425 426 final List<JumpStatement> jumpStatements = new ArrayList<>(); 427 428 int getSplitStateIndex(final JumpStatement jump) { 429 if (jump instanceof BreakNode && jump.getLabelName() == null) { 430 // Unlabelled break is a special case 431 hasBreak = true; 432 return BREAK_STATE; 433 } 434 435 int i = 0; 436 for(final JumpStatement exJump: jumpStatements) { 437 if (jump.getClass() == exJump.getClass() && Objects.equals(jump.getLabelName(), exJump.getLabelName())) { 438 return i + FIRST_JUMP_STATE; 439 } 440 ++i; 441 } 442 jumpStatements.add(jump); 443 return i + FIRST_JUMP_STATE; 444 } 445 446 SplitState(final SplitNode splitNode) { 447 this.splitNode = splitNode; 448 } 449 } 450} 451