nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java
changeset 28690 78317797ab62
parent 27976 ef54dfb4fc7d
child 28784 a79ac84f2ea4
equal deleted inserted replaced
28597:b2f9702efbe9 28690:78317797ab62
    54 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
    54 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
    55 import jdk.nashorn.internal.ir.IdentNode;
    55 import jdk.nashorn.internal.ir.IdentNode;
    56 import jdk.nashorn.internal.ir.IfNode;
    56 import jdk.nashorn.internal.ir.IfNode;
    57 import jdk.nashorn.internal.ir.IndexNode;
    57 import jdk.nashorn.internal.ir.IndexNode;
    58 import jdk.nashorn.internal.ir.JumpStatement;
    58 import jdk.nashorn.internal.ir.JumpStatement;
       
    59 import jdk.nashorn.internal.ir.JumpToInlinedFinally;
    59 import jdk.nashorn.internal.ir.LabelNode;
    60 import jdk.nashorn.internal.ir.LabelNode;
    60 import jdk.nashorn.internal.ir.LexicalContext;
    61 import jdk.nashorn.internal.ir.LexicalContext;
    61 import jdk.nashorn.internal.ir.LiteralNode;
    62 import jdk.nashorn.internal.ir.LiteralNode;
       
    63 import jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode;
    62 import jdk.nashorn.internal.ir.LoopNode;
    64 import jdk.nashorn.internal.ir.LoopNode;
    63 import jdk.nashorn.internal.ir.Node;
    65 import jdk.nashorn.internal.ir.Node;
    64 import jdk.nashorn.internal.ir.ReturnNode;
    66 import jdk.nashorn.internal.ir.ReturnNode;
    65 import jdk.nashorn.internal.ir.RuntimeNode;
    67 import jdk.nashorn.internal.ir.RuntimeNode;
    66 import jdk.nashorn.internal.ir.Statement;
    68 import jdk.nashorn.internal.ir.Statement;
   113 
   115 
   114                 final List<Statement> statements = super.popStatements();
   116                 final List<Statement> statements = super.popStatements();
   115                 for (final Statement statement : statements) {
   117                 for (final Statement statement : statements) {
   116                     if (!terminated) {
   118                     if (!terminated) {
   117                         newStatements.add(statement);
   119                         newStatements.add(statement);
   118                         if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why?
   120                         if (statement.isTerminal() || statement instanceof JumpStatement) { //TODO hasGoto? But some Loops are hasGoto too - why?
   119                             terminated = true;
   121                             terminated = true;
   120                         }
   122                         }
   121                     } else {
   123                     } else {
   122                         statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   124                         statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   123                             @Override
   125                             @Override
   181         addStatement(continueNode);
   183         addStatement(continueNode);
   182         return false;
   184         return false;
   183     }
   185     }
   184 
   186 
   185     @Override
   187     @Override
       
   188     public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
       
   189         addStatement(jumpToInlinedFinally);
       
   190         return false;
       
   191     }
       
   192 
       
   193     @Override
   186     public boolean enterEmptyNode(final EmptyNode emptyNode) {
   194     public boolean enterEmptyNode(final EmptyNode emptyNode) {
   187         return false;
   195         return false;
   188     }
   196     }
   189 
   197 
   190     @Override
   198     @Override
   316     @Override
   324     @Override
   317     public Node leaveThrowNode(final ThrowNode throwNode) {
   325     public Node leaveThrowNode(final ThrowNode throwNode) {
   318         return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
   326         return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
   319     }
   327     }
   320 
   328 
   321     private static Node ensureUniqueNamesIn(final Node node) {
   329     private static <T extends Node> T ensureUniqueNamesIn(final T node) {
   322         return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   330         return (T)node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   323             @Override
   331             @Override
   324             public Node leaveFunctionNode(final FunctionNode functionNode) {
   332             public Node leaveFunctionNode(final FunctionNode functionNode) {
   325                 final String name = functionNode.getName();
   333                 final String name = functionNode.getName();
   326                 return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name));
   334                 return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name));
   327             }
   335             }
   331                 return labelledNode.ensureUniqueLabels(lc);
   339                 return labelledNode.ensureUniqueLabels(lc);
   332             }
   340             }
   333         });
   341         });
   334     }
   342     }
   335 
   343 
   336     private static List<Statement> copyFinally(final Block finallyBody) {
   344     private static Block createFinallyBlock(final Block finallyBody) {
   337         final List<Statement> newStatements = new ArrayList<>();
   345         final List<Statement> newStatements = new ArrayList<>();
   338         for (final Statement statement : finallyBody.getStatements()) {
   346         for (final Statement statement : finallyBody.getStatements()) {
   339             newStatements.add((Statement)ensureUniqueNamesIn(statement));
   347             newStatements.add(statement);
   340             if (statement.hasTerminalFlags()) {
   348             if (statement.hasTerminalFlags()) {
   341                 return newStatements;
   349                 break;
   342             }
   350             }
   343         }
   351         }
   344         return newStatements;
   352         return finallyBody.setStatements(null, newStatements);
   345     }
   353     }
   346 
   354 
   347     private Block catchAllBlock(final TryNode tryNode) {
   355     private Block catchAllBlock(final TryNode tryNode) {
   348         final int  lineNumber = tryNode.getLineNumber();
   356         final int  lineNumber = tryNode.getLineNumber();
   349         final long token      = tryNode.getToken();
   357         final long token      = tryNode.getToken();
   365     private IdentNode compilerConstant(final CompilerConstants cc) {
   373     private IdentNode compilerConstant(final CompilerConstants cc) {
   366         final FunctionNode functionNode = lc.getCurrentFunction();
   374         final FunctionNode functionNode = lc.getCurrentFunction();
   367         return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
   375         return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
   368     }
   376     }
   369 
   377 
   370     private static boolean isTerminal(final List<Statement> statements) {
   378     private static boolean isTerminalFinally(final Block finallyBlock) {
   371         return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
   379         return finallyBlock.getLastStatement().hasTerminalFlags();
   372     }
   380     }
   373 
   381 
   374     /**
   382     /**
   375      * Splice finally code into all endpoints of a trynode
   383      * Splice finally code into all endpoints of a trynode
   376      * @param tryNode the try node
   384      * @param tryNode the try node
   377      * @param rethrows list of rethrowing throw nodes from synthetic catch blocks
   385      * @param rethrow the rethrowing throw nodes from the synthetic catch block
   378      * @param finallyBody the code in the original finally block
   386      * @param finallyBody the code in the original finally block
   379      * @return new try node after splicing finally code (same if nop)
   387      * @return new try node after splicing finally code (same if nop)
   380      */
   388      */
   381     private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
   389     private TryNode spliceFinally(final TryNode tryNode, final ThrowNode rethrow, final Block finallyBody) {
   382         assert tryNode.getFinallyBody() == null;
   390         assert tryNode.getFinallyBody() == null;
   383 
   391 
       
   392         final Block finallyBlock = createFinallyBlock(finallyBody);
       
   393         final ArrayList<Block> inlinedFinallies = new ArrayList<>();
       
   394         final FunctionNode fn = lc.getCurrentFunction();
   384         final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   395         final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   385             final List<Node> insideTry = new ArrayList<>();
       
   386 
       
   387             @Override
       
   388             public boolean enterDefault(final Node node) {
       
   389                 insideTry.add(node);
       
   390                 return true;
       
   391             }
       
   392 
   396 
   393             @Override
   397             @Override
   394             public boolean enterFunctionNode(final FunctionNode functionNode) {
   398             public boolean enterFunctionNode(final FunctionNode functionNode) {
   395                 // do not enter function nodes - finally code should not be inlined into them
   399                 // do not enter function nodes - finally code should not be inlined into them
   396                 return false;
   400                 return false;
   397             }
   401             }
   398 
   402 
   399             @Override
   403             @Override
   400             public Node leaveThrowNode(final ThrowNode throwNode) {
   404             public Node leaveThrowNode(final ThrowNode throwNode) {
   401                 if (rethrows.contains(throwNode)) {
   405                 if (rethrow == throwNode) {
   402                     final List<Statement> newStatements = copyFinally(finallyBody);
   406                     return new BlockStatement(prependFinally(finallyBlock, throwNode));
   403                     if (!isTerminal(newStatements)) {
       
   404                         newStatements.add(throwNode);
       
   405                     }
       
   406                     return BlockStatement.createReplacement(throwNode, newStatements);
       
   407                 }
   407                 }
   408                 return throwNode;
   408                 return throwNode;
   409             }
   409             }
   410 
   410 
   411             @Override
   411             @Override
   417             public Node leaveContinueNode(final ContinueNode continueNode) {
   417             public Node leaveContinueNode(final ContinueNode continueNode) {
   418                 return leaveJumpStatement(continueNode);
   418                 return leaveJumpStatement(continueNode);
   419             }
   419             }
   420 
   420 
   421             private Node leaveJumpStatement(final JumpStatement jump) {
   421             private Node leaveJumpStatement(final JumpStatement jump) {
   422                 return copy(jump, (Node)jump.getTarget(Lower.this.lc));
   422                 // NOTE: leaveJumpToInlinedFinally deliberately does not delegate to this method, only break and
       
   423                 // continue are edited. JTIF nodes should not be changed, rather the surroundings of
       
   424                 // break/continue/return that were moved into the inlined finally block itself will be changed.
       
   425 
       
   426                 // If this visitor's lc doesn't find the target of the jump, it means it's external to the try block.
       
   427                 if (jump.getTarget(lc) == null) {
       
   428                     return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, jump));
       
   429                 }
       
   430                 return jump;
   423             }
   431             }
   424 
   432 
   425             @Override
   433             @Override
   426             public Node leaveReturnNode(final ReturnNode returnNode) {
   434             public Node leaveReturnNode(final ReturnNode returnNode) {
   427                 final Expression expr  = returnNode.getExpression();
   435                 final Expression expr = returnNode.getExpression();
   428                 final List<Statement> newStatements = new ArrayList<>();
   436                 if (isTerminalFinally(finallyBlock)) {
   429 
   437                     if (expr == null) {
   430                 final Expression resultNode;
   438                         // Terminal finally; no return expression.
   431                 if (expr != null) {
   439                         return createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock));
   432                     //we need to evaluate the result of the return in case it is complex while
   440                     }
   433                     //still in the try block, store it in a result value and return it afterwards
   441                     // Terminal finally; has a return expression.
   434                     resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
   442                     final List<Statement> newStatements = new ArrayList<>(2);
   435                     newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
   443                     final int retLineNumber = returnNode.getLineNumber();
       
   444                     final long retToken = returnNode.getToken();
       
   445                     // Expression is evaluated for side effects.
       
   446                     newStatements.add(new ExpressionStatement(retLineNumber, retToken, returnNode.getFinish(), expr));
       
   447                     newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock)));
       
   448                     return new BlockStatement(retLineNumber, new Block(retToken, finallyBlock.getFinish(), newStatements));
       
   449                 } else if (expr == null || expr instanceof PrimitiveLiteralNode<?> || (expr instanceof IdentNode && RETURN.symbolName().equals(((IdentNode)expr).getName()))) {
       
   450                     // Nonterminal finally; no return expression, or returns a primitive literal, or returns :return.
       
   451                     // Just move the return expression into the finally block.
       
   452                     return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode));
   436                 } else {
   453                 } else {
   437                     resultNode = null;
   454                     // We need to evaluate the result of the return in case it is complex while still in the try block,
       
   455                     // store it in :return, and return it afterwards.
       
   456                     final List<Statement> newStatements = new ArrayList<>();
       
   457                     final int retLineNumber = returnNode.getLineNumber();
       
   458                     final long retToken = returnNode.getToken();
       
   459                     final int retFinish = returnNode.getFinish();
       
   460                     final Expression resultNode = new IdentNode(expr.getToken(), expr.getFinish(), RETURN.symbolName());
       
   461                     // ":return = <expr>;"
       
   462                     newStatements.add(new ExpressionStatement(retLineNumber, retToken, retFinish, new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
       
   463                     // inline finally and end it with "return :return;"
       
   464                     newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode.setExpression(resultNode))));
       
   465                     return new BlockStatement(retLineNumber, new Block(retToken, retFinish, newStatements));
   438                 }
   466                 }
   439 
       
   440                 newStatements.addAll(copyFinally(finallyBody));
       
   441                 if (!isTerminal(newStatements)) {
       
   442                     newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
       
   443                 }
       
   444 
       
   445                 return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements);
       
   446             }
       
   447 
       
   448             private Node copy(final Statement endpoint, final Node targetNode) {
       
   449                 if (!insideTry.contains(targetNode)) {
       
   450                     final List<Statement> newStatements = copyFinally(finallyBody);
       
   451                     if (!isTerminal(newStatements)) {
       
   452                         newStatements.add(endpoint);
       
   453                     }
       
   454                     return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements);
       
   455                 }
       
   456                 return endpoint;
       
   457             }
   467             }
   458         });
   468         });
   459 
   469         addStatement(inlinedFinallies.isEmpty() ? newTryNode : newTryNode.setInlinedFinallies(lc, inlinedFinallies));
   460         addStatement(newTryNode);
   470         // TODO: if finallyStatement is terminal, we could just have sites of inlined finallies jump here.
   461         for (final Node statement : finallyBody.getStatements()) {
   471         addStatement(new BlockStatement(finallyBlock));
   462             addStatement((Statement)statement);
       
   463         }
       
   464 
   472 
   465         return newTryNode;
   473         return newTryNode;
       
   474     }
       
   475 
       
   476     private static JumpToInlinedFinally createJumpToInlinedFinally(final FunctionNode fn, final List<Block> inlinedFinallies, final Block finallyBlock) {
       
   477         final String labelName = fn.uniqueName(":finally");
       
   478         final long token = finallyBlock.getToken();
       
   479         final int finish = finallyBlock.getFinish();
       
   480         inlinedFinallies.add(new Block(token, finish, new LabelNode(finallyBlock.getFirstStatementLineNumber(),
       
   481                 token, finish, labelName, finallyBlock)));
       
   482         return new JumpToInlinedFinally(labelName);
       
   483     }
       
   484 
       
   485     private static Block prependFinally(final Block finallyBlock, final Statement statement) {
       
   486         final Block inlinedFinally = ensureUniqueNamesIn(finallyBlock);
       
   487         if (isTerminalFinally(finallyBlock)) {
       
   488             return inlinedFinally;
       
   489         }
       
   490         final List<Statement> stmts = inlinedFinally.getStatements();
       
   491         final List<Statement> newStmts = new ArrayList<>(stmts.size() + 1);
       
   492         newStmts.addAll(stmts);
       
   493         newStmts.add(statement);
       
   494         return new Block(inlinedFinally.getToken(), statement.getFinish(), newStmts);
   466     }
   495     }
   467 
   496 
   468     @Override
   497     @Override
   469     public Node leaveTryNode(final TryNode tryNode) {
   498     public Node leaveTryNode(final TryNode tryNode) {
   470         final Block finallyBody = tryNode.getFinallyBody();
   499         final Block finallyBody = tryNode.getFinallyBody();
   471 
   500         TryNode newTryNode = tryNode.setFinallyBody(lc, null);
   472         if (finallyBody == null) {
   501 
   473             return addStatement(ensureUnconditionalCatch(tryNode));
   502         // No finally or empty finally
       
   503         if (finallyBody == null || finallyBody.getStatementCount() == 0) {
       
   504             final List<CatchNode> catches = newTryNode.getCatches();
       
   505             if (catches == null || catches.isEmpty()) {
       
   506                 // A completely degenerate try block: empty finally, no catches. Replace it with try body.
       
   507                 return addStatement(new BlockStatement(tryNode.getBody()));
       
   508             }
       
   509             return addStatement(ensureUnconditionalCatch(newTryNode));
   474         }
   510         }
   475 
   511 
   476         /*
   512         /*
   477          * create a new trynode
   513          * create a new trynode
   478          *    if we have catches:
   514          *    if we have catches:
   494          *
   530          *
   495          *
   531          *
   496          *   now splice in finally code wherever needed
   532          *   now splice in finally code wherever needed
   497          *
   533          *
   498          */
   534          */
   499         TryNode newTryNode;
       
   500 
       
   501         final Block catchAll = catchAllBlock(tryNode);
   535         final Block catchAll = catchAllBlock(tryNode);
   502 
   536 
   503         final List<ThrowNode> rethrows = new ArrayList<>();
   537         final List<ThrowNode> rethrows = new ArrayList<>(1);
   504         catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   538         catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   505             @Override
   539             @Override
   506             public boolean enterThrowNode(final ThrowNode throwNode) {
   540             public boolean enterThrowNode(final ThrowNode throwNode) {
   507                 rethrows.add(throwNode);
   541                 rethrows.add(throwNode);
   508                 return true;
   542                 return true;
   509             }
   543             }
   510         });
   544         });
   511         assert rethrows.size() == 1;
   545         assert rethrows.size() == 1;
   512 
   546 
   513         if (tryNode.getCatchBlocks().isEmpty()) {
   547         if (!tryNode.getCatchBlocks().isEmpty()) {
   514             newTryNode = tryNode.setFinallyBody(null);
   548             final Block outerBody = new Block(newTryNode.getToken(), newTryNode.getFinish(), ensureUnconditionalCatch(newTryNode));
   515         } else {
   549             newTryNode = newTryNode.setBody(lc, outerBody).setCatchBlocks(lc, null);
   516             final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null)));
   550         }
   517             newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
   551 
   518         }
   552         newTryNode = newTryNode.setCatchBlocks(lc, Arrays.asList(catchAll));
   519 
       
   520         newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
       
   521 
   553 
   522         /*
   554         /*
   523          * Now that the transform is done, we have to go into the try and splice
   555          * Now that the transform is done, we have to go into the try and splice
   524          * the finally block in front of any statement that is outside the try
   556          * the finally block in front of any statement that is outside the try
   525          */
   557          */
   526         return spliceFinally(newTryNode, rethrows, finallyBody);
   558         return (TryNode)lc.replace(tryNode, spliceFinally(newTryNode, rethrows.get(0), finallyBody));
   527     }
   559     }
   528 
   560 
   529     private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
   561     private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
   530         final List<CatchNode> catches = tryNode.getCatches();
   562         final List<CatchNode> catches = tryNode.getCatches();
   531         if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) {
   563         if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) {
   533         }
   565         }
   534         // If the last catch block is conditional, add an unconditional rethrow block
   566         // If the last catch block is conditional, add an unconditional rethrow block
   535         final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
   567         final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
   536 
   568 
   537         newCatchBlocks.add(catchAllBlock(tryNode));
   569         newCatchBlocks.add(catchAllBlock(tryNode));
   538         return tryNode.setCatchBlocks(newCatchBlocks);
   570         return tryNode.setCatchBlocks(lc, newCatchBlocks);
   539     }
   571     }
   540 
   572 
   541     @Override
   573     @Override
   542     public Node leaveVarNode(final VarNode varNode) {
   574     public Node leaveVarNode(final VarNode varNode) {
   543         addStatement(varNode);
   575         addStatement(varNode);