8133300: Ensure symbol table immutability in Nashorn AST

Mon, 31 Aug 2015 15:18:59 +0200

author
attila
date
Mon, 31 Aug 2015 15:18:59 +0200
changeset 1536
bb0fac71c1da
parent 1535
8f28ca037c57
child 1537
ecb9327a3854

8133300: Ensure symbol table immutability in Nashorn AST
Reviewed-by: hannesw, lagergren

src/jdk/nashorn/internal/codegen/AssignSymbols.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/AstSerializer.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/CacheAst.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/CompilationPhase.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/Compiler.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/FindScopeDepths.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/Label.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/Block.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/ForNode.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/FunctionNode.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/SwitchNode.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/Symbol.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/TryNode.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/AstSerializer.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/CompiledFunction.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java file | annotate | diff | comparison | revisions
     1.1 --- a/src/jdk/nashorn/internal/codegen/AssignSymbols.java	Mon Aug 24 09:12:35 2015 +0200
     1.2 +++ b/src/jdk/nashorn/internal/codegen/AssignSymbols.java	Mon Aug 31 15:18:59 2015 +0200
     1.3 @@ -148,12 +148,14 @@
     1.4      private final Deque<Set<String>> thisProperties = new ArrayDeque<>();
     1.5      private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol
     1.6      private final Compiler compiler;
     1.7 +    private final boolean isOnDemand;
     1.8  
     1.9      public AssignSymbols(final Compiler compiler) {
    1.10          super(new LexicalContext());
    1.11          this.compiler = compiler;
    1.12          this.log   = initLogger(compiler.getContext());
    1.13          this.debug = log.isEnabled();
    1.14 +        this.isOnDemand = compiler.isOnDemandCompilation();
    1.15      }
    1.16  
    1.17      @Override
    1.18 @@ -389,7 +391,7 @@
    1.19  
    1.20              // Create and add to appropriate block.
    1.21              symbol = createSymbol(name, flags);
    1.22 -            symbolBlock.putSymbol(lc, symbol);
    1.23 +            symbolBlock.putSymbol(symbol);
    1.24  
    1.25              if ((flags & IS_SCOPE) == 0) {
    1.26                  // Initial assumption; symbol can lose its slot later
    1.27 @@ -439,7 +441,7 @@
    1.28          start(block);
    1.29  
    1.30          if (lc.isFunctionBody()) {
    1.31 -            block.clearSymbols();
    1.32 +            assert !block.hasSymbols();
    1.33              final FunctionNode fn = lc.getCurrentFunction();
    1.34              if (isUnparsedFunction(fn)) {
    1.35                  // It's a skipped nested function. Just mark the symbols being used by it as being in use.
    1.36 @@ -458,7 +460,7 @@
    1.37      }
    1.38  
    1.39      private boolean isUnparsedFunction(final FunctionNode fn) {
    1.40 -        return compiler.isOnDemandCompilation() && fn != lc.getOutermostFunction();
    1.41 +        return isOnDemand && fn != lc.getOutermostFunction();
    1.42      }
    1.43  
    1.44      @Override
    1.45 @@ -746,28 +748,6 @@
    1.46          }
    1.47      }
    1.48  
    1.49 -    @Override
    1.50 -    public Node leaveBlock(final Block block) {
    1.51 -        // It's not necessary to guard the marking of symbols as locals with this "if" condition for
    1.52 -        // correctness, it's just an optimization -- runtime type calculation is not used when the compilation
    1.53 -        // is not an on-demand optimistic compilation, so we can skip locals marking then.
    1.54 -        if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) {
    1.55 -            // OTOH, we must not declare symbols from nested functions to be locals. As we're doing on-demand
    1.56 -            // compilation, and we're skipping parsing the function bodies for nested functions, this
    1.57 -            // basically only means their parameters. It'd be enough to mistakenly declare to be a local a
    1.58 -            // symbol in the outer function named the same as one of the parameters, though.
    1.59 -            if (lc.getFunction(block) == lc.getOutermostFunction()) {
    1.60 -                for (final Symbol symbol: block.getSymbols()) {
    1.61 -                    if (!symbol.isScope()) {
    1.62 -                        assert symbol.isVar() || symbol.isParam();
    1.63 -                        compiler.declareLocalSymbol(symbol.getName());
    1.64 -                    }
    1.65 -                }
    1.66 -            }
    1.67 -        }
    1.68 -        return block;
    1.69 -    }
    1.70 -
    1.71      private Node leaveDELETE(final UnaryNode unaryNode) {
    1.72          final FunctionNode currentFunctionNode = lc.getCurrentFunction();
    1.73          final boolean      strictMode          = currentFunctionNode.isStrict();
    1.74 @@ -785,9 +765,9 @@
    1.75  
    1.76              if (symbol.isThis()) {
    1.77                  // Can't delete "this", ignore and return true
    1.78 -                return LiteralNode.newInstance(unaryNode, true).accept(this);
    1.79 +                return LiteralNode.newInstance(unaryNode, true);
    1.80              }
    1.81 -            final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this);
    1.82 +            final Expression literalNode = LiteralNode.newInstance(unaryNode, name);
    1.83              final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel())));
    1.84  
    1.85              if (!failDelete) {
    1.86 @@ -798,7 +778,7 @@
    1.87  
    1.88              if (failDelete) {
    1.89                  request = Request.FAIL_DELETE;
    1.90 -            } else if (symbol.isGlobal() && !symbol.isFunctionDeclaration()) {
    1.91 +            } else if ((symbol.isGlobal() && !symbol.isFunctionDeclaration()) || symbol.isProgramLevel()) {
    1.92                  request = Request.SLOW_DELETE;
    1.93              }
    1.94          } else if (rhs instanceof AccessNode) {
    1.95 @@ -806,7 +786,7 @@
    1.96              final String     property = ((AccessNode)rhs).getProperty();
    1.97  
    1.98              args.add(base);
    1.99 -            args.add((Expression)LiteralNode.newInstance(unaryNode, property).accept(this));
   1.100 +            args.add(LiteralNode.newInstance(unaryNode, property));
   1.101              args.add(strictFlagNode);
   1.102  
   1.103          } else if (rhs instanceof IndexNode) {
   1.104 @@ -819,15 +799,15 @@
   1.105              args.add(strictFlagNode);
   1.106  
   1.107          } else {
   1.108 -            return LiteralNode.newInstance(unaryNode, true).accept(this);
   1.109 +            return LiteralNode.newInstance(unaryNode, true);
   1.110          }
   1.111 -        return new RuntimeNode(unaryNode, request, args).accept(this);
   1.112 +        return new RuntimeNode(unaryNode, request, args);
   1.113      }
   1.114  
   1.115      @Override
   1.116      public Node leaveForNode(final ForNode forNode) {
   1.117          if (forNode.isForIn()) {
   1.118 -            forNode.setIterator(newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
   1.119 +            return forNode.setIterator(lc, newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
   1.120          }
   1.121  
   1.122          return end(forNode);
   1.123 @@ -903,19 +883,18 @@
   1.124      public Node leaveSwitchNode(final SwitchNode switchNode) {
   1.125          // We only need a symbol for the tag if it's not an integer switch node
   1.126          if(!switchNode.isUniqueInteger()) {
   1.127 -            switchNode.setTag(newObjectInternal(SWITCH_TAG_PREFIX));
   1.128 +            return switchNode.setTag(lc, newObjectInternal(SWITCH_TAG_PREFIX));
   1.129          }
   1.130          return switchNode;
   1.131      }
   1.132  
   1.133      @Override
   1.134      public Node leaveTryNode(final TryNode tryNode) {
   1.135 -        tryNode.setException(exceptionSymbol());
   1.136          assert tryNode.getFinallyBody() == null;
   1.137  
   1.138          end(tryNode);
   1.139  
   1.140 -        return tryNode;
   1.141 +        return tryNode.setException(lc, exceptionSymbol());
   1.142      }
   1.143  
   1.144      private Node leaveTYPEOF(final UnaryNode unaryNode) {
   1.145 @@ -924,13 +903,13 @@
   1.146          final List<Expression> args = new ArrayList<>();
   1.147          if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) {
   1.148              args.add(compilerConstantIdentifier(SCOPE));
   1.149 -            args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null
   1.150 +            args.add(LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName())); //null
   1.151          } else {
   1.152              args.add(rhs);
   1.153 -            args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
   1.154 +            args.add(LiteralNode.newInstance(unaryNode)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
   1.155          }
   1.156  
   1.157 -        final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args).accept(this);
   1.158 +        final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args);
   1.159  
   1.160          end(unaryNode);
   1.161  
   1.162 @@ -938,7 +917,7 @@
   1.163      }
   1.164  
   1.165      private FunctionNode markProgramBlock(final FunctionNode functionNode) {
   1.166 -        if (compiler.isOnDemandCompilation() || !functionNode.isProgram()) {
   1.167 +        if (isOnDemand || !functionNode.isProgram()) {
   1.168              return functionNode;
   1.169          }
   1.170  
     2.1 --- a/src/jdk/nashorn/internal/codegen/AstSerializer.java	Mon Aug 24 09:12:35 2015 +0200
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,73 +0,0 @@
     2.4 -/*
     2.5 - * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
     2.6 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     2.7 - *
     2.8 - * This code is free software; you can redistribute it and/or modify it
     2.9 - * under the terms of the GNU General Public License version 2 only, as
    2.10 - * published by the Free Software Foundation.  Oracle designates this
    2.11 - * particular file as subject to the "Classpath" exception as provided
    2.12 - * by Oracle in the LICENSE file that accompanied this code.
    2.13 - *
    2.14 - * This code is distributed in the hope that it will be useful, but WITHOUT
    2.15 - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    2.16 - * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    2.17 - * version 2 for more details (a copy is included in the LICENSE file that
    2.18 - * accompanied this code).
    2.19 - *
    2.20 - * You should have received a copy of the GNU General Public License version
    2.21 - * 2 along with this work; if not, write to the Free Software Foundation,
    2.22 - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    2.23 - *
    2.24 - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    2.25 - * or visit www.oracle.com if you need additional information or have any
    2.26 - * questions.
    2.27 - */
    2.28 -package jdk.nashorn.internal.codegen;
    2.29 -
    2.30 -import java.io.ByteArrayOutputStream;
    2.31 -import java.io.IOException;
    2.32 -import java.io.ObjectOutputStream;
    2.33 -import java.util.Collections;
    2.34 -import java.util.zip.Deflater;
    2.35 -import java.util.zip.DeflaterOutputStream;
    2.36 -import jdk.nashorn.internal.ir.Block;
    2.37 -import jdk.nashorn.internal.ir.FunctionNode;
    2.38 -import jdk.nashorn.internal.ir.LexicalContext;
    2.39 -import jdk.nashorn.internal.ir.Node;
    2.40 -import jdk.nashorn.internal.ir.Statement;
    2.41 -import jdk.nashorn.internal.ir.visitor.NodeVisitor;
    2.42 -import jdk.nashorn.internal.runtime.options.Options;
    2.43 -
    2.44 -/**
    2.45 - * This static utility class performs serialization of FunctionNode ASTs to a byte array.
    2.46 - * The format is a standard Java serialization stream, deflated.
    2.47 - */
    2.48 -final class AstSerializer {
    2.49 -    // Experimentally, we concluded that compression level 4 gives a good tradeoff between serialization speed
    2.50 -    // and size.
    2.51 -    private static final int COMPRESSION_LEVEL = Options.getIntProperty("nashorn.serialize.compression", 4);
    2.52 -    static byte[] serialize(final FunctionNode fn) {
    2.53 -        final ByteArrayOutputStream out = new ByteArrayOutputStream();
    2.54 -        final Deflater deflater = new Deflater(COMPRESSION_LEVEL);
    2.55 -        try (final ObjectOutputStream oout = new ObjectOutputStream(new DeflaterOutputStream(out, deflater))) {
    2.56 -            oout.writeObject(removeInnerFunctionBodies(fn));
    2.57 -        } catch (final IOException e) {
    2.58 -            throw new AssertionError("Unexpected exception serializing function", e);
    2.59 -        } finally {
    2.60 -            deflater.end();
    2.61 -        }
    2.62 -        return out.toByteArray();
    2.63 -    }
    2.64 -
    2.65 -    private static FunctionNode removeInnerFunctionBodies(final FunctionNode fn) {
    2.66 -        return (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
    2.67 -            @Override
    2.68 -            public Node leaveBlock(final Block block) {
    2.69 -                if (lc.isFunctionBody() && lc.getFunction(block) != lc.getOutermostFunction()) {
    2.70 -                    return block.setStatements(lc, Collections.<Statement>emptyList());
    2.71 -                }
    2.72 -                return super.leaveBlock(block);
    2.73 -            }
    2.74 -        });
    2.75 -    }
    2.76 -}
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/src/jdk/nashorn/internal/codegen/CacheAst.java	Mon Aug 31 15:18:59 2015 +0200
     3.3 @@ -0,0 +1,87 @@
     3.4 +/*
     3.5 + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
     3.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3.7 + *
     3.8 + * This code is free software; you can redistribute it and/or modify it
     3.9 + * under the terms of the GNU General Public License version 2 only, as
    3.10 + * published by the Free Software Foundation.  Oracle designates this
    3.11 + * particular file as subject to the "Classpath" exception as provided
    3.12 + * by Oracle in the LICENSE file that accompanied this code.
    3.13 + *
    3.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
    3.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    3.16 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    3.17 + * version 2 for more details (a copy is included in the LICENSE file that
    3.18 + * accompanied this code).
    3.19 + *
    3.20 + * You should have received a copy of the GNU General Public License version
    3.21 + * 2 along with this work; if not, write to the Free Software Foundation,
    3.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    3.23 + *
    3.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    3.25 + * or visit www.oracle.com if you need additional information or have any
    3.26 + * questions.
    3.27 + */
    3.28 +
    3.29 +package jdk.nashorn.internal.codegen;
    3.30 +
    3.31 +import java.util.ArrayDeque;
    3.32 +import java.util.Collections;
    3.33 +import java.util.Deque;
    3.34 +import jdk.nashorn.internal.ir.FunctionNode;
    3.35 +import jdk.nashorn.internal.ir.LexicalContext;
    3.36 +import jdk.nashorn.internal.ir.Node;
    3.37 +import jdk.nashorn.internal.ir.Statement;
    3.38 +import jdk.nashorn.internal.ir.visitor.NodeVisitor;
    3.39 +import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
    3.40 +
    3.41 +class CacheAst extends NodeVisitor<LexicalContext> {
    3.42 +    private final Deque<RecompilableScriptFunctionData> dataStack = new ArrayDeque<>();
    3.43 +
    3.44 +    private final Compiler compiler;
    3.45 +
    3.46 +    CacheAst(final Compiler compiler) {
    3.47 +        super(new LexicalContext());
    3.48 +        this.compiler = compiler;
    3.49 +        assert !compiler.isOnDemandCompilation();
    3.50 +    }
    3.51 +
    3.52 +    @Override
    3.53 +    public boolean enterFunctionNode(final FunctionNode functionNode) {
    3.54 +        final int id = functionNode.getId();
    3.55 +        // It isn't necessary to keep a stack of RecompilableScriptFunctionData, but then we'd need to do a
    3.56 +        // potentially transitive lookup with compiler.getScriptFunctionData(id) for deeper functions; this way
    3.57 +        // we keep it constant time.
    3.58 +        dataStack.push(dataStack.isEmpty() ? compiler.getScriptFunctionData(id) : dataStack.peek().getScriptFunctionData(id));
    3.59 +        return true;
    3.60 +    }
    3.61 +
    3.62 +    @Override
    3.63 +    public Node leaveFunctionNode(final FunctionNode functionNode) {
    3.64 +        final RecompilableScriptFunctionData data = dataStack.pop();
    3.65 +        if (functionNode.isSplit()) {
    3.66 +            // NOTE: cache only split function ASTs from eager pass. Caching non-split functions would require
    3.67 +            // some additional work, namely creating the concept of "uncacheable" function and reworking
    3.68 +            // ApplySpecialization to ensure that functions undergoing apply-to-call transformations are not
    3.69 +            // cacheable as well as recomputing Symbol.useCount when caching the eagerly parsed AST.
    3.70 +            // Recomputing Symbol.useCount would be needed so it will only reflect uses from within the
    3.71 +            // function being cached (and not reflect uses from its own nested functions or functions it is
    3.72 +            // nested in). This is consistent with the count an on-demand recompilation of the function would
    3.73 +            // produce. This is important as the decision to emit shared scope calls is based on this count,
    3.74 +            // and if it is not matched between a previous version of the code and its deoptimizing rest-of
    3.75 +            // compilation, it can result in rest-of not emitting a shared scope call where a previous version
    3.76 +            // of the code (compiled from a cached eager pre-pass seeing higher (global) useCount) would emit
    3.77 +            // it, causing a mismatch in stack shapes between previous code and its rest-of.
    3.78 +            data.setCachedAst(functionNode);
    3.79 +        }
    3.80 +
    3.81 +        if (!dataStack.isEmpty() && ((dataStack.peek().getFunctionFlags() & FunctionNode.IS_SPLIT) != 0)) {
    3.82 +            // Return a function node with no body so that caching outer functions doesn't hold on to nested
    3.83 +            // functions' bodies. Note we're doing this only for functions directly nested inside split
    3.84 +            // functions, since we're only caching the split ones. It is not necessary to limit body removal
    3.85 +            // to just these functions, but it's a cheap way to prevent unnecessary AST mutations.
    3.86 +            return functionNode.setBody(lc, functionNode.getBody().setStatements(null, Collections.<Statement>emptyList()));
    3.87 +        }
    3.88 +        return functionNode;
    3.89 +    }
    3.90 +}
     4.1 --- a/src/jdk/nashorn/internal/codegen/CompilationPhase.java	Mon Aug 24 09:12:35 2015 +0200
     4.2 +++ b/src/jdk/nashorn/internal/codegen/CompilationPhase.java	Mon Aug 31 15:18:59 2015 +0200
     4.3 @@ -28,18 +28,18 @@
     4.4  import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
     4.5  
     4.6  import java.io.PrintWriter;
     4.7 -import java.util.EnumSet;
     4.8  import java.util.HashMap;
     4.9  import java.util.LinkedHashMap;
    4.10  import java.util.Map;
    4.11  import java.util.Map.Entry;
    4.12  import java.util.Set;
    4.13 -import jdk.nashorn.internal.AssertsEnabled;
    4.14  import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
    4.15 +import jdk.nashorn.internal.ir.Block;
    4.16  import jdk.nashorn.internal.ir.FunctionNode;
    4.17  import jdk.nashorn.internal.ir.LexicalContext;
    4.18  import jdk.nashorn.internal.ir.LiteralNode;
    4.19  import jdk.nashorn.internal.ir.Node;
    4.20 +import jdk.nashorn.internal.ir.Symbol;
    4.21  import jdk.nashorn.internal.ir.debug.ASTWriter;
    4.22  import jdk.nashorn.internal.ir.debug.PrintVisitor;
    4.23  import jdk.nashorn.internal.ir.visitor.NodeVisitor;
    4.24 @@ -159,27 +159,28 @@
    4.25  
    4.26      static final CompilationPhase PROGRAM_POINT_PHASE = new ProgramPointPhase();
    4.27  
    4.28 -    private static final class SerializeSplitPhase extends CompilationPhase {
    4.29 +    private static final class CacheAstPhase extends CompilationPhase {
    4.30          @Override
    4.31          FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
    4.32 -            return transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
    4.33 -                @Override
    4.34 -                public boolean enterFunctionNode(final FunctionNode functionNode) {
    4.35 -                    if (functionNode.isSplit()) {
    4.36 -                        compiler.serializeAst(functionNode);
    4.37 -                    }
    4.38 -                    return true;
    4.39 -                }
    4.40 -            });
    4.41 +            if (!compiler.isOnDemandCompilation()) {
    4.42 +                // Only do this on initial preprocessing of the source code. For on-demand compilations from
    4.43 +                // source, FindScopeDepths#leaveFunctionNode() calls data.setCachedAst() for the sole function
    4.44 +                // being compiled.
    4.45 +                transformFunction(fn, new CacheAst(compiler));
    4.46 +            }
    4.47 +            // NOTE: we're returning the original fn as we have destructively modified the cached functions by
    4.48 +            // removing their bodies. This step is associating FunctionNode objects with
    4.49 +            // RecompilableScriptFunctionData; it's not really modifying the AST.
    4.50 +            return fn;
    4.51          }
    4.52  
    4.53          @Override
    4.54          public String toString() {
    4.55 -            return "'Serialize Split Functions'";
    4.56 +            return "'Cache ASTs'";
    4.57          }
    4.58      };
    4.59  
    4.60 -    static final CompilationPhase SERIALIZE_SPLIT_PHASE = new SerializeSplitPhase();
    4.61 +    static final CompilationPhase CACHE_AST_PHASE = new CacheAstPhase();
    4.62  
    4.63      private static final class SymbolAssignmentPhase extends CompilationPhase {
    4.64          @Override
    4.65 @@ -209,6 +210,44 @@
    4.66  
    4.67      static final CompilationPhase SCOPE_DEPTH_COMPUTATION_PHASE = new ScopeDepthComputationPhase();
    4.68  
    4.69 +    private static final class DeclareLocalSymbolsPhase extends CompilationPhase {
    4.70 +        @Override
    4.71 +        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
    4.72 +            // It's not necessary to guard the marking of symbols as locals with this "if" condition for
    4.73 +            // correctness, it's just an optimization -- runtime type calculation is not used when the compilation
    4.74 +            // is not an on-demand optimistic compilation, so we can skip locals marking then.
    4.75 +            if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) {
    4.76 +                fn.getBody().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
    4.77 +                    @Override
    4.78 +                    public boolean enterFunctionNode(final FunctionNode functionNode) {
    4.79 +                        // OTOH, we must not declare symbols from nested functions to be locals. As we're doing on-demand
    4.80 +                        // compilation, and we're skipping parsing the function bodies for nested functions, this
    4.81 +                        // basically only means their parameters. It'd be enough to mistakenly declare to be a local a
    4.82 +                        // symbol in the outer function named the same as one of the parameters, though.
    4.83 +                        return false;
    4.84 +                    };
    4.85 +                    @Override
    4.86 +                    public boolean enterBlock(final Block block) {
    4.87 +                        for (final Symbol symbol: block.getSymbols()) {
    4.88 +                            if (!symbol.isScope()) {
    4.89 +                                compiler.declareLocalSymbol(symbol.getName());
    4.90 +                            }
    4.91 +                        }
    4.92 +                        return true;
    4.93 +                    };
    4.94 +                });
    4.95 +            }
    4.96 +            return fn;
    4.97 +        }
    4.98 +
    4.99 +        @Override
   4.100 +        public String toString() {
   4.101 +            return "'Local Symbols Declaration'";
   4.102 +        }
   4.103 +    };
   4.104 +
   4.105 +    static final CompilationPhase DECLARE_LOCAL_SYMBOLS_PHASE = new DeclareLocalSymbolsPhase();
   4.106 +
   4.107      private static final class OptimisticTypeAssignmentPhase extends CompilationPhase {
   4.108          @Override
   4.109          FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
   4.110 @@ -311,7 +350,7 @@
   4.111       */
   4.112      static final CompilationPhase REUSE_COMPILE_UNITS_PHASE = new ReuseCompileUnitsPhase();
   4.113  
   4.114 -    private static final class ReinitializeSerializedPhase extends CompilationPhase {
   4.115 +    private static final class ReinitializeCachedPhase extends CompilationPhase {
   4.116          @Override
   4.117          FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
   4.118              final Set<CompileUnit> unitSet = CompileUnit.createCompileUnitSet();
   4.119 @@ -352,11 +391,11 @@
   4.120  
   4.121          @Override
   4.122          public String toString() {
   4.123 -            return "'Deserialize'";
   4.124 +            return "'Reinitialize cached'";
   4.125          }
   4.126      }
   4.127  
   4.128 -    static final CompilationPhase REINITIALIZE_SERIALIZED = new ReinitializeSerializedPhase();
   4.129 +    static final CompilationPhase REINITIALIZE_CACHED = new ReinitializeCachedPhase();
   4.130  
   4.131      private static final class BytecodeGenerationPhase extends CompilationPhase {
   4.132          @Override
     5.1 --- a/src/jdk/nashorn/internal/codegen/Compiler.java	Mon Aug 24 09:12:35 2015 +0200
     5.2 +++ b/src/jdk/nashorn/internal/codegen/Compiler.java	Mon Aug 31 15:18:59 2015 +0200
     5.3 @@ -160,42 +160,40 @@
     5.4       */
     5.5      private static final int COMPILE_UNIT_NAME_BUFFER_SIZE = 32;
     5.6  
     5.7 -    private final Map<Integer, byte[]> serializedAsts = new HashMap<>();
     5.8 -
     5.9      /**
    5.10       * Compilation phases that a compilation goes through
    5.11       */
    5.12      public static class CompilationPhases implements Iterable<CompilationPhase> {
    5.13  
    5.14          /**
    5.15 -         * Singleton that describes compilation up to the phase where a function can be serialized.
    5.16 +         * Singleton that describes compilation up to the phase where a function can be cached.
    5.17           */
    5.18 -        private final static CompilationPhases COMPILE_UPTO_SERIALIZABLE = new CompilationPhases(
    5.19 +        private final static CompilationPhases COMPILE_UPTO_CACHED = new CompilationPhases(
    5.20                  "Common initial phases",
    5.21                  CompilationPhase.CONSTANT_FOLDING_PHASE,
    5.22                  CompilationPhase.LOWERING_PHASE,
    5.23                  CompilationPhase.APPLY_SPECIALIZATION_PHASE,
    5.24                  CompilationPhase.SPLITTING_PHASE,
    5.25                  CompilationPhase.PROGRAM_POINT_PHASE,
    5.26 -                CompilationPhase.SERIALIZE_SPLIT_PHASE
    5.27 +                CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
    5.28 +                CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
    5.29 +                CompilationPhase.CACHE_AST_PHASE
    5.30                  );
    5.31  
    5.32 -        private final static CompilationPhases COMPILE_SERIALIZABLE_UPTO_BYTECODE = new CompilationPhases(
    5.33 +        private final static CompilationPhases COMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
    5.34                  "After common phases, before bytecode generator",
    5.35 -                CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
    5.36 -                CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
    5.37                  CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
    5.38                  CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE
    5.39                  );
    5.40  
    5.41          /**
    5.42 -         * Singleton that describes additional steps to be taken after deserializing, all the way up to (but not
    5.43 -         * including) generating and installing code.
    5.44 +         * Singleton that describes additional steps to be taken after retrieving a cached function, all the
    5.45 +         * way up to (but not including) generating and installing code.
    5.46           */
    5.47 -        public final static CompilationPhases RECOMPILE_SERIALIZED_UPTO_BYTECODE = new CompilationPhases(
    5.48 -                "Recompile serialized function up to bytecode",
    5.49 -                CompilationPhase.REINITIALIZE_SERIALIZED,
    5.50 -                COMPILE_SERIALIZABLE_UPTO_BYTECODE
    5.51 +        public final static CompilationPhases RECOMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
    5.52 +                "Recompile cached function up to bytecode",
    5.53 +                CompilationPhase.REINITIALIZE_CACHED,
    5.54 +                COMPILE_CACHED_UPTO_BYTECODE
    5.55                  );
    5.56  
    5.57          /**
    5.58 @@ -211,8 +209,8 @@
    5.59          /** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */
    5.60          public final static CompilationPhases COMPILE_UPTO_BYTECODE = new CompilationPhases(
    5.61                  "Compile upto bytecode",
    5.62 -                COMPILE_UPTO_SERIALIZABLE,
    5.63 -                COMPILE_SERIALIZABLE_UPTO_BYTECODE);
    5.64 +                COMPILE_UPTO_CACHED,
    5.65 +                COMPILE_CACHED_UPTO_BYTECODE);
    5.66  
    5.67          /** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */
    5.68          public final static CompilationPhases COMPILE_ALL_NO_INSTALL = new CompilationPhases(
    5.69 @@ -227,9 +225,9 @@
    5.70                  GENERATE_BYTECODE_AND_INSTALL);
    5.71  
    5.72          /** Singleton that describes a full compilation - this includes code installation - from serialized state*/
    5.73 -        public final static CompilationPhases COMPILE_ALL_SERIALIZED = new CompilationPhases(
    5.74 +        public final static CompilationPhases COMPILE_ALL_CACHED = new CompilationPhases(
    5.75                  "Eager compilation from serializaed state",
    5.76 -                RECOMPILE_SERIALIZED_UPTO_BYTECODE,
    5.77 +                RECOMPILE_CACHED_UPTO_BYTECODE,
    5.78                  GENERATE_BYTECODE_AND_INSTALL);
    5.79  
    5.80          /**
    5.81 @@ -248,9 +246,9 @@
    5.82                  GENERATE_BYTECODE_AND_INSTALL_RESTOF);
    5.83  
    5.84          /** Compile from serialized for a rest of method */
    5.85 -        public final static CompilationPhases COMPILE_SERIALIZED_RESTOF = new CompilationPhases(
    5.86 +        public final static CompilationPhases COMPILE_CACHED_RESTOF = new CompilationPhases(
    5.87                  "Compile serialized, rest of",
    5.88 -                RECOMPILE_SERIALIZED_UPTO_BYTECODE,
    5.89 +                RECOMPILE_CACHED_UPTO_BYTECODE,
    5.90                  GENERATE_BYTECODE_AND_INSTALL_RESTOF);
    5.91  
    5.92          private final List<CompilationPhase> phases;
    5.93 @@ -313,7 +311,7 @@
    5.94          }
    5.95  
    5.96          boolean isRestOfCompilation() {
    5.97 -            return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_SERIALIZED_RESTOF;
    5.98 +            return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_CACHED_RESTOF;
    5.99          }
   5.100  
   5.101          String getDesc() {
   5.102 @@ -766,14 +764,6 @@
   5.103          compileUnits.addAll(newUnits);
   5.104      }
   5.105  
   5.106 -    void serializeAst(final FunctionNode fn) {
   5.107 -        serializedAsts.put(fn.getId(), AstSerializer.serialize(fn));
   5.108 -    }
   5.109 -
   5.110 -    byte[] removeSerializedAst(final int fnId) {
   5.111 -        return serializedAsts.remove(fnId);
   5.112 -    }
   5.113 -
   5.114      CompileUnit findUnit(final long weight) {
   5.115          for (final CompileUnit unit : compileUnits) {
   5.116              if (unit.canHold(weight)) {
     6.1 --- a/src/jdk/nashorn/internal/codegen/FindScopeDepths.java	Mon Aug 24 09:12:35 2015 +0200
     6.2 +++ b/src/jdk/nashorn/internal/codegen/FindScopeDepths.java	Mon Aug 31 15:18:59 2015 +0200
     6.3 @@ -188,6 +188,9 @@
     6.4                  log.fine("Reviving scriptfunction ", quote(name), " as defined in previous (now lost) dynamic scope.");
     6.5                  newFunctionNode = newFunctionNode.setInDynamicContext(lc);
     6.6              }
     6.7 +            if (newFunctionNode == lc.getOutermostFunction() && !newFunctionNode.hasApplyToCallSpecialization()) {
     6.8 +                data.setCachedAst(newFunctionNode);
     6.9 +            }
    6.10              return newFunctionNode;
    6.11          }
    6.12  
    6.13 @@ -208,8 +211,7 @@
    6.14                  ObjectClassGenerator.createAllocationStrategy(newFunctionNode.getThisProperties(), compiler.getContext().useDualFields()),
    6.15                  nestedFunctions,
    6.16                  externalSymbolDepths.get(fnId),
    6.17 -                internalSymbols.get(fnId),
    6.18 -                compiler.removeSerializedAst(fnId));
    6.19 +                internalSymbols.get(fnId));
    6.20  
    6.21          if (lc.getOutermostFunction() != newFunctionNode) {
    6.22              final FunctionNode parentFn = lc.getParentFunction(newFunctionNode);
     7.1 --- a/src/jdk/nashorn/internal/codegen/Label.java	Mon Aug 24 09:12:35 2015 +0200
     7.2 +++ b/src/jdk/nashorn/internal/codegen/Label.java	Mon Aug 31 15:18:59 2015 +0200
     7.3 @@ -497,7 +497,7 @@
     7.4      private transient Label.Stack stack;
     7.5  
     7.6      /** ASM representation of this label */
     7.7 -    private jdk.internal.org.objectweb.asm.Label label;
     7.8 +    private transient jdk.internal.org.objectweb.asm.Label label;
     7.9  
    7.10      /** Id for debugging purposes, remove if footprint becomes unmanageable */
    7.11      private final int id;
     8.1 --- a/src/jdk/nashorn/internal/ir/Block.java	Mon Aug 24 09:12:35 2015 +0200
     8.2 +++ b/src/jdk/nashorn/internal/ir/Block.java	Mon Aug 31 15:18:59 2015 +0200
     8.3 @@ -130,11 +130,42 @@
     8.4      }
     8.5  
     8.6      /**
     8.7 -     * Clear the symbols in the block.
     8.8 -     * TODO: make this immutable.
     8.9 +     * Returns true if this block defines any symbols.
    8.10 +     * @return true if this block defines any symbols.
    8.11       */
    8.12 -    public void clearSymbols() {
    8.13 -        symbols.clear();
    8.14 +    public boolean hasSymbols() {
    8.15 +        return !symbols.isEmpty();
    8.16 +    }
    8.17 +
    8.18 +    /**
    8.19 +     * Replaces symbols defined in this block with different symbols. Used to ensure symbol tables are
    8.20 +     * immutable upon construction and have copy-on-write semantics. Note that this method only replaces the
    8.21 +     * symbols in the symbol table, it does not act on any contained AST nodes that might reference the symbols.
    8.22 +     * Those should be updated separately as this method is meant to be used as part of such an update pass.
    8.23 +     * @param lc the current lexical context
    8.24 +     * @param replacements the map of symbol replacements
    8.25 +     * @return a new block with replaced symbols, or this block if none of the replacements modified the symbol
    8.26 +     * table.
    8.27 +     */
    8.28 +    public Block replaceSymbols(final LexicalContext lc, final Map<Symbol, Symbol> replacements) {
    8.29 +        if (symbols.isEmpty()) {
    8.30 +            return this;
    8.31 +        }
    8.32 +        final LinkedHashMap<String, Symbol> newSymbols = new LinkedHashMap<>(symbols);
    8.33 +        for (final Map.Entry<String, Symbol> entry: newSymbols.entrySet()) {
    8.34 +            final Symbol newSymbol = replacements.get(entry.getValue());
    8.35 +            assert newSymbol != null : "Missing replacement for " + entry.getKey();
    8.36 +            entry.setValue(newSymbol);
    8.37 +        }
    8.38 +        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, newSymbols, conversion));
    8.39 +    }
    8.40 +
    8.41 +    /**
    8.42 +     * Returns a copy of this block with a shallow copy of the symbol table.
    8.43 +     * @return a copy of this block with a shallow copy of the symbol table.
    8.44 +     */
    8.45 +    public Block copyWithNewSymbols() {
    8.46 +        return new Block(this, finish, statements, flags, new LinkedHashMap<>(symbols), conversion);
    8.47      }
    8.48  
    8.49      @Override
    8.50 @@ -162,7 +193,7 @@
    8.51       * @return symbol iterator
    8.52       */
    8.53      public List<Symbol> getSymbols() {
    8.54 -        return Collections.unmodifiableList(new ArrayList<>(symbols.values()));
    8.55 +        return symbols.isEmpty() ? Collections.<Symbol>emptyList() : Collections.unmodifiableList(new ArrayList<>(symbols.values()));
    8.56      }
    8.57  
    8.58      /**
    8.59 @@ -326,10 +357,9 @@
    8.60      /**
    8.61       * Add or overwrite an existing symbol in the block
    8.62       *
    8.63 -     * @param lc     get lexical context
    8.64       * @param symbol symbol
    8.65       */
    8.66 -    public void putSymbol(final LexicalContext lc, final Symbol symbol) {
    8.67 +    public void putSymbol(final Symbol symbol) {
    8.68          symbols.put(symbol.getName(), symbol);
    8.69      }
    8.70  
     9.1 --- a/src/jdk/nashorn/internal/ir/ForNode.java	Mon Aug 24 09:12:35 2015 +0200
     9.2 +++ b/src/jdk/nashorn/internal/ir/ForNode.java	Mon Aug 31 15:18:59 2015 +0200
     9.3 @@ -43,7 +43,7 @@
     9.4      private final JoinPredecessorExpression modify;
     9.5  
     9.6      /** Iterator symbol. */
     9.7 -    private Symbol iterator;
     9.8 +    private final Symbol iterator;
     9.9  
    9.10      /** Is this a normal for in loop? */
    9.11      public static final int IS_FOR_IN           = 1 << 0;
    9.12 @@ -70,22 +70,22 @@
    9.13          this.flags  = flags;
    9.14          this.init = null;
    9.15          this.modify = null;
    9.16 +        this.iterator = null;
    9.17      }
    9.18  
    9.19      private ForNode(final ForNode forNode, final Expression init, final JoinPredecessorExpression test,
    9.20 -            final Block body, final JoinPredecessorExpression modify, final int flags, final boolean controlFlowEscapes, final LocalVariableConversion conversion) {
    9.21 +            final Block body, final JoinPredecessorExpression modify, final int flags,
    9.22 +            final boolean controlFlowEscapes, final LocalVariableConversion conversion, final Symbol iterator) {
    9.23          super(forNode, test, body, controlFlowEscapes, conversion);
    9.24          this.init   = init;
    9.25          this.modify = modify;
    9.26          this.flags  = flags;
    9.27 -        // Even if the for node gets cloned in try/finally, the symbol can be shared as only one branch of the finally
    9.28 -        // is executed.
    9.29 -        this.iterator = forNode.iterator;
    9.30 +        this.iterator = iterator;
    9.31      }
    9.32  
    9.33      @Override
    9.34      public Node ensureUniqueLabels(final LexicalContext lc) {
    9.35 -        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
    9.36 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
    9.37      }
    9.38  
    9.39      @Override
    9.40 @@ -158,7 +158,7 @@
    9.41          if (this.init == init) {
    9.42              return this;
    9.43          }
    9.44 -        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
    9.45 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
    9.46      }
    9.47  
    9.48      /**
    9.49 @@ -206,10 +206,15 @@
    9.50  
    9.51      /**
    9.52       * Assign an iterator symbol to this ForNode. Used for for in and for each constructs
    9.53 +     * @param lc the current lexical context
    9.54       * @param iterator the iterator symbol
    9.55 +     * @return a ForNode with the iterator set
    9.56       */
    9.57 -    public void setIterator(final Symbol iterator) {
    9.58 -        this.iterator = iterator;
    9.59 +    public ForNode setIterator(final LexicalContext lc, final Symbol iterator) {
    9.60 +        if (this.iterator == iterator) {
    9.61 +            return this;
    9.62 +        }
    9.63 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
    9.64      }
    9.65  
    9.66      /**
    9.67 @@ -230,7 +235,7 @@
    9.68          if (this.modify == modify) {
    9.69              return this;
    9.70          }
    9.71 -        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
    9.72 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
    9.73      }
    9.74  
    9.75      @Override
    9.76 @@ -238,7 +243,7 @@
    9.77          if (this.test == test) {
    9.78              return this;
    9.79          }
    9.80 -        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
    9.81 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
    9.82      }
    9.83  
    9.84      @Override
    9.85 @@ -251,7 +256,7 @@
    9.86          if (this.body == body) {
    9.87              return this;
    9.88          }
    9.89 -        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
    9.90 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
    9.91      }
    9.92  
    9.93      @Override
    9.94 @@ -259,19 +264,19 @@
    9.95          if (this.controlFlowEscapes == controlFlowEscapes) {
    9.96              return this;
    9.97          }
    9.98 -        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
    9.99 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
   9.100      }
   9.101  
   9.102      private ForNode setFlags(final LexicalContext lc, final int flags) {
   9.103          if (this.flags == flags) {
   9.104              return this;
   9.105          }
   9.106 -        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
   9.107 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
   9.108      }
   9.109  
   9.110      @Override
   9.111      JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
   9.112 -        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
   9.113 +        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
   9.114      }
   9.115  
   9.116      @Override
    10.1 --- a/src/jdk/nashorn/internal/ir/FunctionNode.java	Mon Aug 24 09:12:35 2015 +0200
    10.2 +++ b/src/jdk/nashorn/internal/ir/FunctionNode.java	Mon Aug 31 15:18:59 2015 +0200
    10.3 @@ -223,6 +223,11 @@
    10.4       */
    10.5      public static final int NEEDS_CALLEE       = 1 << 26;
    10.6  
    10.7 +    /**
    10.8 +     * Is the function node cached?
    10.9 +     */
   10.10 +    public static final int IS_CACHED = 1 << 27;
   10.11 +
   10.12      /** extension callsite flags mask */
   10.13      public static final int EXTENSION_CALLSITE_FLAGS = IS_PRINT_PARSE |
   10.14          IS_PRINT_LOWER_PARSE | IS_PRINT_AST | IS_PRINT_LOWER_AST |
   10.15 @@ -302,7 +307,7 @@
   10.16          final List<IdentNode> parameters,
   10.17          final int thisProperties,
   10.18          final Class<?> rootClass,
   10.19 -        final Source source, Namespace namespace) {
   10.20 +        final Source source, final Namespace namespace) {
   10.21          super(functionNode);
   10.22  
   10.23          this.endParserState    = endParserState;
   10.24 @@ -629,7 +634,7 @@
   10.25       */
   10.26      public boolean needsCallee() {
   10.27          // NOTE: we only need isSplit() here to ensure that :scope can never drop below slot 2 for splitting array units.
   10.28 -        return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict()) || hasOptimisticApplyToCall();
   10.29 +        return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict()) || hasApplyToCallSpecialization();
   10.30      }
   10.31  
   10.32      /**
   10.33 @@ -646,7 +651,7 @@
   10.34       * Return true if function contains an apply to call transform
   10.35       * @return true if this function has transformed apply to call
   10.36       */
   10.37 -    public boolean hasOptimisticApplyToCall() {
   10.38 +    public boolean hasApplyToCallSpecialization() {
   10.39          return getFlag(HAS_APPLY_TO_CALL_SPECIALIZATION);
   10.40      }
   10.41  
   10.42 @@ -1095,6 +1100,24 @@
   10.43      }
   10.44  
   10.45      /**
   10.46 +     * Returns true if this function node has been cached.
   10.47 +     * @return true if this function node has been cached.
   10.48 +     */
   10.49 +    public boolean isCached() {
   10.50 +        return getFlag(IS_CACHED);
   10.51 +    }
   10.52 +
   10.53 +    /**
   10.54 +     * Mark this function node as having been cached.
   10.55 +     * @param lc the current lexical context
   10.56 +     * @return a function node equivalent to this one, with the flag set.
   10.57 +     */
   10.58 +    public FunctionNode setCached(final LexicalContext lc) {
   10.59 +        return setFlag(lc, IS_CACHED);
   10.60 +    }
   10.61 +
   10.62 +
   10.63 +    /**
   10.64       * Get the compile unit used to compile this function
   10.65       * @see Compiler
   10.66       * @return the compile unit
    11.1 --- a/src/jdk/nashorn/internal/ir/SwitchNode.java	Mon Aug 24 09:12:35 2015 +0200
    11.2 +++ b/src/jdk/nashorn/internal/ir/SwitchNode.java	Mon Aug 31 15:18:59 2015 +0200
    11.3 @@ -53,7 +53,7 @@
    11.4      private final boolean uniqueInteger;
    11.5  
    11.6      /** Tag symbol. */
    11.7 -    private Symbol tag;
    11.8 +    private final Symbol tag;
    11.9  
   11.10      /**
   11.11       * Constructor
   11.12 @@ -71,15 +71,16 @@
   11.13          this.cases            = cases;
   11.14          this.defaultCaseIndex = defaultCase == null ? -1 : cases.indexOf(defaultCase);
   11.15          this.uniqueInteger    = false;
   11.16 +        this.tag = null;
   11.17      }
   11.18  
   11.19      private SwitchNode(final SwitchNode switchNode, final Expression expression, final List<CaseNode> cases,
   11.20 -            final int defaultCaseIndex, final LocalVariableConversion conversion, final boolean uniqueInteger) {
   11.21 +            final int defaultCaseIndex, final LocalVariableConversion conversion, final boolean uniqueInteger, final Symbol tag) {
   11.22          super(switchNode, conversion);
   11.23          this.expression       = expression;
   11.24          this.cases            = cases;
   11.25          this.defaultCaseIndex = defaultCaseIndex;
   11.26 -        this.tag              = switchNode.getTag(); //TODO are symbols inherited as references?
   11.27 +        this.tag              = tag;
   11.28          this.uniqueInteger    = uniqueInteger;
   11.29      }
   11.30  
   11.31 @@ -89,7 +90,7 @@
   11.32          for (final CaseNode caseNode : cases) {
   11.33              newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody(), caseNode.getLocalVariableConversion()));
   11.34          }
   11.35 -        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion, uniqueInteger));
   11.36 +        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion, uniqueInteger, tag));
   11.37      }
   11.38  
   11.39      @Override
   11.40 @@ -157,7 +158,7 @@
   11.41          if (this.cases == cases) {
   11.42              return this;
   11.43          }
   11.44 -        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
   11.45 +        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
   11.46      }
   11.47  
   11.48      /**
   11.49 @@ -189,7 +190,7 @@
   11.50          if (this.expression == expression) {
   11.51              return this;
   11.52          }
   11.53 -        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
   11.54 +        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
   11.55      }
   11.56  
   11.57      /**
   11.58 @@ -204,10 +205,15 @@
   11.59      /**
   11.60       * Set the tag symbol for this switch. The tag symbol is where
   11.61       * the switch expression result is stored
   11.62 +     * @param lc lexical context
   11.63       * @param tag a symbol
   11.64 +     * @return a switch node with the symbol set
   11.65       */
   11.66 -    public void setTag(final Symbol tag) {
   11.67 -        this.tag = tag;
   11.68 +    public SwitchNode setTag(final LexicalContext lc, final Symbol tag) {
   11.69 +        if (this.tag == tag) {
   11.70 +            return this;
   11.71 +        }
   11.72 +        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
   11.73      }
   11.74  
   11.75      /**
   11.76 @@ -229,12 +235,12 @@
   11.77          if(this.uniqueInteger == uniqueInteger) {
   11.78              return this;
   11.79          }
   11.80 -        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
   11.81 +        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
   11.82      }
   11.83  
   11.84      @Override
   11.85      JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
   11.86 -        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
   11.87 +        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
   11.88      }
   11.89  
   11.90  }
    12.1 --- a/src/jdk/nashorn/internal/ir/Symbol.java	Mon Aug 24 09:12:35 2015 +0200
    12.2 +++ b/src/jdk/nashorn/internal/ir/Symbol.java	Mon Aug 31 15:18:59 2015 +0200
    12.3 @@ -25,7 +25,10 @@
    12.4  
    12.5  package jdk.nashorn.internal.ir;
    12.6  
    12.7 +import java.io.IOException;
    12.8 +import java.io.ObjectInputStream;
    12.9  import java.io.PrintWriter;
   12.10 +import java.io.Serializable;
   12.11  import java.util.HashSet;
   12.12  import java.util.Set;
   12.13  import java.util.StringTokenizer;
   12.14 @@ -47,7 +50,9 @@
   12.15   * refer to their location.
   12.16   */
   12.17  
   12.18 -public final class Symbol implements Comparable<Symbol> {
   12.19 +public final class Symbol implements Comparable<Symbol>, Cloneable, Serializable {
   12.20 +    private static final long serialVersionUID = 1L;
   12.21 +
   12.22      /** Is this Global */
   12.23      public static final int IS_GLOBAL   = 1;
   12.24      /** Is this a variable */
   12.25 @@ -94,10 +99,10 @@
   12.26  
   12.27      /** First bytecode method local variable slot for storing the value(s) of this variable. -1 indicates the variable
   12.28       * is not stored in local variable slots or it is not yet known. */
   12.29 -    private int firstSlot = -1;
   12.30 +    private transient int firstSlot = -1;
   12.31  
   12.32      /** Field number in scope or property; array index in varargs when not using arguments object. */
   12.33 -    private int fieldIndex = -1;
   12.34 +    private transient int fieldIndex = -1;
   12.35  
   12.36      /** Number of times this symbol is used in code */
   12.37      private int useCount;
   12.38 @@ -144,6 +149,15 @@
   12.39          }
   12.40      }
   12.41  
   12.42 +    @Override
   12.43 +    public Symbol clone() {
   12.44 +        try {
   12.45 +            return (Symbol)super.clone();
   12.46 +        } catch (final CloneNotSupportedException e) {
   12.47 +            throw new AssertionError(e);
   12.48 +        }
   12.49 +    }
   12.50 +
   12.51      private static String align(final String string, final int max) {
   12.52          final StringBuilder sb = new StringBuilder();
   12.53          sb.append(string.substring(0, Math.min(string.length(), max)));
   12.54 @@ -337,7 +351,7 @@
   12.55       * Flag this symbol as scope as described in {@link Symbol#isScope()}
   12.56       * @return the symbol
   12.57       */
   12.58 -     public Symbol setIsScope() {
   12.59 +    public Symbol setIsScope() {
   12.60          if (!isScope()) {
   12.61              if(shouldTrace()) {
   12.62                  trace("SET IS SCOPE");
   12.63 @@ -609,11 +623,11 @@
   12.64  
   12.65      /**
   12.66       * Increase the symbol's use count by one.
   12.67 -     * @return the symbol
   12.68       */
   12.69 -    public Symbol increaseUseCount() {
   12.70 -        useCount++;
   12.71 -        return this;
   12.72 +    public void increaseUseCount() {
   12.73 +        if (isScope()) { // Avoid dirtying a cache line; we only need the use count for scoped symbols
   12.74 +            useCount++;
   12.75 +        }
   12.76      }
   12.77  
   12.78      /**
   12.79 @@ -669,4 +683,10 @@
   12.80              new Throwable().printStackTrace(Context.getCurrentErr());
   12.81          }
   12.82      }
   12.83 +
   12.84 +    private void readObject(final ObjectInputStream in) throws ClassNotFoundException, IOException {
   12.85 +        in.defaultReadObject();
   12.86 +        firstSlot = -1;
   12.87 +        fieldIndex = -1;
   12.88 +    }
   12.89  }
    13.1 --- a/src/jdk/nashorn/internal/ir/TryNode.java	Mon Aug 24 09:12:35 2015 +0200
    13.2 +++ b/src/jdk/nashorn/internal/ir/TryNode.java	Mon Aug 31 15:18:59 2015 +0200
    13.3 @@ -65,7 +65,7 @@
    13.4      private final List<Block> inlinedFinallies;
    13.5  
    13.6      /** Exception symbol. */
    13.7 -    private Symbol exception;
    13.8 +    private final Symbol exception;
    13.9  
   13.10      private final LocalVariableConversion conversion;
   13.11  
   13.12 @@ -86,22 +86,23 @@
   13.13          this.finallyBody = finallyBody;
   13.14          this.conversion  = null;
   13.15          this.inlinedFinallies = Collections.emptyList();
   13.16 +        this.exception = null;
   13.17      }
   13.18  
   13.19 -    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies) {
   13.20 +    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies, final Symbol exception) {
   13.21          super(tryNode);
   13.22          this.body        = body;
   13.23          this.catchBlocks = catchBlocks;
   13.24          this.finallyBody = finallyBody;
   13.25          this.conversion  = conversion;
   13.26          this.inlinedFinallies = inlinedFinallies;
   13.27 -        this.exception = tryNode.exception;
   13.28 +        this.exception = exception;
   13.29      }
   13.30  
   13.31      @Override
   13.32      public Node ensureUniqueLabels(final LexicalContext lc) {
   13.33          //try nodes are never in lex context
   13.34 -        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
   13.35 +        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception);
   13.36      }
   13.37  
   13.38      @Override
   13.39 @@ -160,7 +161,7 @@
   13.40          if (this.body == body) {
   13.41              return this;
   13.42          }
   13.43 -        return Node.replaceInLexicalContext(lc, this, new TryNode(this,  body, catchBlocks, finallyBody, conversion, inlinedFinallies));
   13.44 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this,  body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
   13.45      }
   13.46  
   13.47      /**
   13.48 @@ -197,7 +198,7 @@
   13.49          if (this.catchBlocks == catchBlocks) {
   13.50              return this;
   13.51          }
   13.52 -        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
   13.53 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
   13.54      }
   13.55  
   13.56      /**
   13.57 @@ -209,12 +210,15 @@
   13.58      }
   13.59      /**
   13.60       * Set the exception symbol for this try block
   13.61 +     * @param lc lexical context
   13.62       * @param exception a symbol for the compiler to store the exception in
   13.63       * @return new TryNode or same if unchanged
   13.64       */
   13.65 -    public TryNode setException(final Symbol exception) {
   13.66 -        this.exception = exception;
   13.67 -        return this;
   13.68 +    public TryNode setException(final LexicalContext lc, final Symbol exception) {
   13.69 +        if (this.exception == exception) {
   13.70 +            return this;
   13.71 +        }
   13.72 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
   13.73      }
   13.74  
   13.75      /**
   13.76 @@ -277,7 +281,7 @@
   13.77          if (this.finallyBody == finallyBody) {
   13.78              return this;
   13.79          }
   13.80 -        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
   13.81 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
   13.82      }
   13.83  
   13.84      /**
   13.85 @@ -293,7 +297,7 @@
   13.86              return this;
   13.87          }
   13.88          assert checkInlinedFinallies(inlinedFinallies);
   13.89 -        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
   13.90 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
   13.91      }
   13.92  
   13.93      private static boolean checkInlinedFinallies(final List<Block> inlinedFinallies) {
   13.94 @@ -314,7 +318,7 @@
   13.95          if(this.conversion == conversion) {
   13.96              return this;
   13.97          }
   13.98 -        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
   13.99 +        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception);
  13.100      }
  13.101  
  13.102      @Override
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/src/jdk/nashorn/internal/runtime/AstSerializer.java	Mon Aug 31 15:18:59 2015 +0200
    14.3 @@ -0,0 +1,55 @@
    14.4 +/*
    14.5 + * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
    14.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    14.7 + *
    14.8 + * This code is free software; you can redistribute it and/or modify it
    14.9 + * under the terms of the GNU General Public License version 2 only, as
   14.10 + * published by the Free Software Foundation.  Oracle designates this
   14.11 + * particular file as subject to the "Classpath" exception as provided
   14.12 + * by Oracle in the LICENSE file that accompanied this code.
   14.13 + *
   14.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
   14.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   14.16 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14.17 + * version 2 for more details (a copy is included in the LICENSE file that
   14.18 + * accompanied this code).
   14.19 + *
   14.20 + * You should have received a copy of the GNU General Public License version
   14.21 + * 2 along with this work; if not, write to the Free Software Foundation,
   14.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   14.23 + *
   14.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   14.25 + * or visit www.oracle.com if you need additional information or have any
   14.26 + * questions.
   14.27 + */
   14.28 +package jdk.nashorn.internal.runtime;
   14.29 +
   14.30 +import java.io.ByteArrayOutputStream;
   14.31 +import java.io.IOException;
   14.32 +import java.io.ObjectOutputStream;
   14.33 +import java.util.zip.Deflater;
   14.34 +import java.util.zip.DeflaterOutputStream;
   14.35 +import jdk.nashorn.internal.ir.FunctionNode;
   14.36 +import jdk.nashorn.internal.runtime.options.Options;
   14.37 +
   14.38 +/**
   14.39 + * This static utility class performs serialization of FunctionNode ASTs to a byte array.
   14.40 + * The format is a standard Java serialization stream, deflated.
   14.41 + */
   14.42 +final class AstSerializer {
   14.43 +    // Experimentally, we concluded that compression level 4 gives a good tradeoff between serialization speed
   14.44 +    // and size.
   14.45 +    private static final int COMPRESSION_LEVEL = Options.getIntProperty("nashorn.serialize.compression", 4);
   14.46 +    static byte[] serialize(final FunctionNode fn) {
   14.47 +        final ByteArrayOutputStream out = new ByteArrayOutputStream();
   14.48 +        final Deflater deflater = new Deflater(COMPRESSION_LEVEL);
   14.49 +        try (final ObjectOutputStream oout = new ObjectOutputStream(new DeflaterOutputStream(out, deflater))) {
   14.50 +            oout.writeObject(fn);
   14.51 +        } catch (final IOException e) {
   14.52 +            throw new AssertionError("Unexpected exception serializing function", e);
   14.53 +        } finally {
   14.54 +            deflater.end();
   14.55 +        }
   14.56 +        return out.toByteArray();
   14.57 +    }
   14.58 +}
    15.1 --- a/src/jdk/nashorn/internal/runtime/CompiledFunction.java	Mon Aug 24 09:12:35 2015 +0200
    15.2 +++ b/src/jdk/nashorn/internal/runtime/CompiledFunction.java	Mon Aug 31 15:18:59 2015 +0200
    15.3 @@ -27,6 +27,7 @@
    15.4  import static jdk.nashorn.internal.lookup.Lookup.MH;
    15.5  import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
    15.6  import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
    15.7 +
    15.8  import java.lang.invoke.CallSite;
    15.9  import java.lang.invoke.MethodHandle;
   15.10  import java.lang.invoke.MethodHandles;
   15.11 @@ -820,7 +821,7 @@
   15.12          // isn't available, we'll use the old one bound into the call site.
   15.13          final OptimismInfo effectiveOptInfo = currentOptInfo != null ? currentOptInfo : oldOptInfo;
   15.14          FunctionNode fn = effectiveOptInfo.reparse();
   15.15 -        final boolean serialized = effectiveOptInfo.isSerialized();
   15.16 +        final boolean cached = fn.isCached();
   15.17          final Compiler compiler = effectiveOptInfo.getCompiler(fn, ct, re); //set to non rest-of
   15.18  
   15.19          if (!shouldRecompile) {
   15.20 @@ -828,11 +829,11 @@
   15.21              // recompiled a deoptimized version for an inner invocation.
   15.22              // We still need to do the rest of from the beginning
   15.23              logRecompile("Rest-of compilation [STANDALONE] ", fn, ct, effectiveOptInfo.invalidatedProgramPoints);
   15.24 -            return restOfHandle(effectiveOptInfo, compiler.compile(fn, serialized ? CompilationPhases.COMPILE_SERIALIZED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
   15.25 +            return restOfHandle(effectiveOptInfo, compiler.compile(fn, cached ? CompilationPhases.COMPILE_CACHED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
   15.26          }
   15.27  
   15.28          logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, ct, effectiveOptInfo.invalidatedProgramPoints);
   15.29 -        fn = compiler.compile(fn, serialized ? CompilationPhases.RECOMPILE_SERIALIZED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE);
   15.30 +        fn = compiler.compile(fn, cached ? CompilationPhases.RECOMPILE_CACHED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE);
   15.31          log.fine("Reusable IR generated");
   15.32  
   15.33          // compile the rest of the function, and install it
   15.34 @@ -956,10 +957,6 @@
   15.35          FunctionNode reparse() {
   15.36              return data.reparse();
   15.37          }
   15.38 -
   15.39 -        boolean isSerialized() {
   15.40 -            return data.isSerialized();
   15.41 -        }
   15.42      }
   15.43  
   15.44      @SuppressWarnings("unused")
    16.1 --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Mon Aug 24 09:12:35 2015 +0200
    16.2 +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Mon Aug 31 15:18:59 2015 +0200
    16.3 @@ -26,16 +26,25 @@
    16.4  package jdk.nashorn.internal.runtime;
    16.5  
    16.6  import static jdk.nashorn.internal.lookup.Lookup.MH;
    16.7 +
    16.8  import java.io.IOException;
    16.9  import java.lang.invoke.MethodHandle;
   16.10  import java.lang.invoke.MethodHandles;
   16.11  import java.lang.invoke.MethodType;
   16.12 +import java.lang.ref.Reference;
   16.13 +import java.lang.ref.SoftReference;
   16.14  import java.util.Collection;
   16.15  import java.util.Collections;
   16.16  import java.util.HashSet;
   16.17 +import java.util.IdentityHashMap;
   16.18  import java.util.Map;
   16.19  import java.util.Set;
   16.20  import java.util.TreeMap;
   16.21 +import java.util.concurrent.ExecutorService;
   16.22 +import java.util.concurrent.LinkedBlockingDeque;
   16.23 +import java.util.concurrent.ThreadFactory;
   16.24 +import java.util.concurrent.ThreadPoolExecutor;
   16.25 +import java.util.concurrent.TimeUnit;
   16.26  import jdk.internal.dynalink.support.NameCodec;
   16.27  import jdk.nashorn.internal.codegen.Compiler;
   16.28  import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
   16.29 @@ -45,8 +54,15 @@
   16.30  import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
   16.31  import jdk.nashorn.internal.codegen.TypeMap;
   16.32  import jdk.nashorn.internal.codegen.types.Type;
   16.33 +import jdk.nashorn.internal.ir.Block;
   16.34 +import jdk.nashorn.internal.ir.ForNode;
   16.35  import jdk.nashorn.internal.ir.FunctionNode;
   16.36 +import jdk.nashorn.internal.ir.IdentNode;
   16.37  import jdk.nashorn.internal.ir.LexicalContext;
   16.38 +import jdk.nashorn.internal.ir.Node;
   16.39 +import jdk.nashorn.internal.ir.SwitchNode;
   16.40 +import jdk.nashorn.internal.ir.Symbol;
   16.41 +import jdk.nashorn.internal.ir.TryNode;
   16.42  import jdk.nashorn.internal.ir.visitor.NodeVisitor;
   16.43  import jdk.nashorn.internal.objects.Global;
   16.44  import jdk.nashorn.internal.parser.Parser;
   16.45 @@ -55,6 +71,7 @@
   16.46  import jdk.nashorn.internal.runtime.logging.DebugLogger;
   16.47  import jdk.nashorn.internal.runtime.logging.Loggable;
   16.48  import jdk.nashorn.internal.runtime.logging.Logger;
   16.49 +import jdk.nashorn.internal.runtime.options.Options;
   16.50  /**
   16.51   * This is a subclass that represents a script function that may be regenerated,
   16.52   * for example with specialization based on call site types, or lazily generated.
   16.53 @@ -66,6 +83,8 @@
   16.54      /** Prefix used for all recompiled script classes */
   16.55      public static final String RECOMPILATION_PREFIX = "Recompilation$";
   16.56  
   16.57 +    private static final ExecutorService astSerializerExecutorService = createAstSerializerExecutorService();
   16.58 +
   16.59      /** Unique function node id for this function node */
   16.60      private final int functionNodeId;
   16.61  
   16.62 @@ -77,8 +96,12 @@
   16.63      /** Source from which FunctionNode was parsed. */
   16.64      private transient Source source;
   16.65  
   16.66 -    /** Serialized, compressed form of the AST. Used by split functions as they can't be reparsed from source. */
   16.67 -    private final byte[] serializedAst;
   16.68 +    /**
   16.69 +     * Cached form of the AST. Either a {@code SerializedAst} object used by split functions as they can't be
   16.70 +     * reparsed from source, or a soft reference to a {@code FunctionNode} for other functions (it is safe
   16.71 +     * to be cleared as they can be reparsed).
   16.72 +     */
   16.73 +    private volatile Object cachedAst;
   16.74  
   16.75      /** Token of this function within the source. */
   16.76      private final long token;
   16.77 @@ -128,7 +151,6 @@
   16.78       * @param nestedFunctions     nested function map
   16.79       * @param externalScopeDepths external scope depths
   16.80       * @param internalSymbols     internal symbols to method, defined in its scope
   16.81 -     * @param serializedAst       a serialized AST representation. Normally only used for split functions.
   16.82       */
   16.83      public RecompilableScriptFunctionData(
   16.84          final FunctionNode functionNode,
   16.85 @@ -136,8 +158,7 @@
   16.86          final AllocationStrategy allocationStrategy,
   16.87          final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
   16.88          final Map<String, Integer> externalScopeDepths,
   16.89 -        final Set<String> internalSymbols,
   16.90 -        final byte[] serializedAst) {
   16.91 +        final Set<String> internalSymbols) {
   16.92  
   16.93          super(functionName(functionNode),
   16.94                Math.min(functionNode.getParameters().size(), MAX_ARITY),
   16.95 @@ -161,7 +182,6 @@
   16.96              nfn.setParent(this);
   16.97          }
   16.98  
   16.99 -        this.serializedAst = serializedAst;
  16.100          createLogger();
  16.101      }
  16.102  
  16.103 @@ -244,7 +264,7 @@
  16.104       * @return parent data, or null if non exists and also null IF UNKNOWN.
  16.105       */
  16.106      public RecompilableScriptFunctionData getParent() {
  16.107 -       return parent;
  16.108 +        return parent;
  16.109      }
  16.110  
  16.111      void setParent(final RecompilableScriptFunctionData parent) {
  16.112 @@ -358,13 +378,11 @@
  16.113          return allocationStrategy.allocate(map);
  16.114      }
  16.115  
  16.116 -    boolean isSerialized() {
  16.117 -        return serializedAst != null;
  16.118 -    }
  16.119 -
  16.120      FunctionNode reparse() {
  16.121 -        if (isSerialized()) {
  16.122 -            return deserialize();
  16.123 +        final FunctionNode cachedFunction = getCachedAst();
  16.124 +        if (cachedFunction != null) {
  16.125 +            assert cachedFunction.isCached();
  16.126 +            return cachedFunction;
  16.127          }
  16.128  
  16.129          final int descPosition = Token.descPosition(token);
  16.130 @@ -391,7 +409,104 @@
  16.131          return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
  16.132      }
  16.133  
  16.134 -    private FunctionNode deserialize() {
  16.135 +    private FunctionNode getCachedAst() {
  16.136 +        final Object lCachedAst = cachedAst;
  16.137 +        // Are we softly caching the AST?
  16.138 +        if (lCachedAst instanceof Reference<?>) {
  16.139 +            final FunctionNode fn = (FunctionNode)((Reference<?>)lCachedAst).get();
  16.140 +            if (fn != null) {
  16.141 +                // Yes we are - this is fast
  16.142 +                return cloneSymbols(fn);
  16.143 +            }
  16.144 +        // Are we strongly caching a serialized AST (for split functions only)?
  16.145 +        } else if (lCachedAst instanceof SerializedAst) {
  16.146 +            final SerializedAst serializedAst = (SerializedAst)lCachedAst;
  16.147 +            // Even so, are we also softly caching the AST?
  16.148 +            final FunctionNode cachedFn = serializedAst.cachedAst.get();
  16.149 +            if (cachedFn != null) {
  16.150 +                // Yes we are - this is fast
  16.151 +                return cloneSymbols(cachedFn);
  16.152 +            }
  16.153 +            final FunctionNode deserializedFn = deserialize(serializedAst.serializedAst);
  16.154 +            // Softly cache after deserialization, maybe next time we won't need to deserialize
  16.155 +            serializedAst.cachedAst = new SoftReference<>(deserializedFn);
  16.156 +            return deserializedFn;
  16.157 +        }
  16.158 +        // No cached representation; return null for reparsing
  16.159 +        return null;
  16.160 +    }
  16.161 +
  16.162 +    /**
  16.163 +     * Sets the AST to cache in this function
  16.164 +     * @param astToCache the new AST to cache
  16.165 +     */
  16.166 +    public void setCachedAst(final FunctionNode astToCache) {
  16.167 +        assert astToCache.getId() == functionNodeId; // same function
  16.168 +        assert !(cachedAst instanceof SerializedAst); // Can't overwrite serialized AST
  16.169 +
  16.170 +        final boolean isSplit = astToCache.isSplit();
  16.171 +        // If we're caching a split function, we're doing it in the eager pass, hence there can be no other
  16.172 +        // cached representation already. In other words, isSplit implies cachedAst == null.
  16.173 +        assert !isSplit || cachedAst == null; //
  16.174 +
  16.175 +        final FunctionNode symbolClonedAst = cloneSymbols(astToCache);
  16.176 +        final Reference<FunctionNode> ref = new SoftReference<>(symbolClonedAst);
  16.177 +        cachedAst = ref;
  16.178 +
  16.179 +        // Asynchronously serialize split functions.
  16.180 +        if (isSplit) {
  16.181 +            astSerializerExecutorService.execute(new Runnable() {
  16.182 +                @Override
  16.183 +                public void run() {
  16.184 +                    cachedAst = new SerializedAst(symbolClonedAst, ref);
  16.185 +                }
  16.186 +            });
  16.187 +        }
  16.188 +    }
  16.189 +
  16.190 +    /**
  16.191 +     * Creates the AST serializer executor service used for in-memory serialization of split functions' ASTs.
  16.192 +     * It is created with an unbounded queue (so it can queue any number of pending tasks). Its core and max
  16.193 +     * threads is the same, but they are all allowed to time out so when there's no work, they can all go
  16.194 +     * away. The threads will be daemons, and they will time out if idle for a minute. Their priority is also
  16.195 +     * slightly lower than normal priority as we'd prefer the CPU to keep running the program; serializing
  16.196 +     * split function is a memory conservation measure (it allows us to release the AST), it can wait a bit.
  16.197 +     * @return an executor service with above described characteristics.
  16.198 +     */
  16.199 +    private static ExecutorService createAstSerializerExecutorService() {
  16.200 +        final int threads = Math.max(1, Options.getIntProperty("nashorn.serialize.threads", Runtime.getRuntime().availableProcessors() / 2));
  16.201 +        final ThreadPoolExecutor service = new ThreadPoolExecutor(threads, threads, 1L, TimeUnit.MINUTES, new LinkedBlockingDeque<Runnable>(),
  16.202 +                new ThreadFactory() {
  16.203 +                    @Override
  16.204 +                    public Thread newThread(final Runnable r) {
  16.205 +                        final Thread t = new Thread(r, "Nashorn AST Serializer");
  16.206 +                        t.setDaemon(true);
  16.207 +                        t.setPriority(Thread.NORM_PRIORITY - 1);
  16.208 +                        return t;
  16.209 +                    }
  16.210 +                });
  16.211 +        service.allowCoreThreadTimeOut(true);
  16.212 +        return service;
  16.213 +    }
  16.214 +
  16.215 +    /**
  16.216 +     * A tuple of a serialized AST and a soft reference to a deserialized AST. This is used to cache split
  16.217 +     * functions. Since split functions are altered from their source form, they can't be reparsed from
  16.218 +     * source. While we could just use the {@code byte[]} representation in {@link RecompilableScriptFunctionData#cachedAst}
  16.219 +     * we're using this tuple instead to also keep a deserialized AST around in memory to cut down on
  16.220 +     * deserialization costs.
  16.221 +     */
  16.222 +    private static class SerializedAst {
  16.223 +        private final byte[] serializedAst;
  16.224 +        private volatile Reference<FunctionNode> cachedAst;
  16.225 +
  16.226 +        SerializedAst(final FunctionNode fn, final Reference<FunctionNode> cachedAst) {
  16.227 +            this.serializedAst = AstSerializer.serialize(fn);
  16.228 +            this.cachedAst = cachedAst;
  16.229 +        }
  16.230 +    }
  16.231 +
  16.232 +    private FunctionNode deserialize(final byte[] serializedAst) {
  16.233          final ScriptEnvironment env = installer.getOwner();
  16.234          final Timing timing = env._timing;
  16.235          final long t1 = System.nanoTime();
  16.236 @@ -402,6 +517,107 @@
  16.237          }
  16.238      }
  16.239  
  16.240 +    private FunctionNode cloneSymbols(final FunctionNode fn) {
  16.241 +        final IdentityHashMap<Symbol, Symbol> symbolReplacements = new IdentityHashMap<>();
  16.242 +        final boolean cached = fn.isCached();
  16.243 +        // blockDefinedSymbols is used to re-mark symbols defined outside the function as global. We only
  16.244 +        // need to do this when we cache an eagerly parsed function (which currently means a split one, as we
  16.245 +        // don't cache non-split functions from the eager pass); those already cached, or those not split
  16.246 +        // don't need this step.
  16.247 +        final Set<Symbol> blockDefinedSymbols = fn.isSplit() && !cached ? Collections.newSetFromMap(new IdentityHashMap<Symbol, Boolean>()) : null;
  16.248 +        FunctionNode newFn = (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
  16.249 +
  16.250 +            private Symbol getReplacement(final Symbol original) {
  16.251 +                if (original == null) {
  16.252 +                    return null;
  16.253 +                }
  16.254 +                final Symbol existingReplacement = symbolReplacements.get(original);
  16.255 +                if (existingReplacement != null) {
  16.256 +                    return existingReplacement;
  16.257 +                }
  16.258 +                final Symbol newReplacement = original.clone();
  16.259 +                symbolReplacements.put(original, newReplacement);
  16.260 +                return newReplacement;
  16.261 +            }
  16.262 +
  16.263 +            @Override
  16.264 +            public Node leaveIdentNode(final IdentNode identNode) {
  16.265 +                final Symbol oldSymbol = identNode.getSymbol();
  16.266 +                if (oldSymbol != null) {
  16.267 +                    final Symbol replacement = getReplacement(oldSymbol);
  16.268 +                    return identNode.setSymbol(replacement);
  16.269 +                }
  16.270 +                return identNode;
  16.271 +            }
  16.272 +
  16.273 +            @Override
  16.274 +            public Node leaveForNode(final ForNode forNode) {
  16.275 +                return ensureUniqueLabels(forNode.setIterator(lc, getReplacement(forNode.getIterator())));
  16.276 +            }
  16.277 +
  16.278 +            @Override
  16.279 +            public Node leaveSwitchNode(final SwitchNode switchNode) {
  16.280 +                return ensureUniqueLabels(switchNode.setTag(lc, getReplacement(switchNode.getTag())));
  16.281 +            }
  16.282 +
  16.283 +            @Override
  16.284 +            public Node leaveTryNode(final TryNode tryNode) {
  16.285 +                return ensureUniqueLabels(tryNode.setException(lc, getReplacement(tryNode.getException())));
  16.286 +            }
  16.287 +
  16.288 +            @Override
  16.289 +            public boolean enterBlock(final Block block) {
  16.290 +                for(final Symbol symbol: block.getSymbols()) {
  16.291 +                    final Symbol replacement = getReplacement(symbol);
  16.292 +                    if (blockDefinedSymbols != null) {
  16.293 +                        blockDefinedSymbols.add(replacement);
  16.294 +                    }
  16.295 +                }
  16.296 +                return true;
  16.297 +            }
  16.298 +
  16.299 +            @Override
  16.300 +            public Node leaveBlock(final Block block) {
  16.301 +                return ensureUniqueLabels(block.replaceSymbols(lc, symbolReplacements));
  16.302 +            }
  16.303 +
  16.304 +            @Override
  16.305 +            public Node leaveFunctionNode(final FunctionNode functionNode) {
  16.306 +                return functionNode.setParameters(lc, functionNode.visitParameters(this));
  16.307 +            }
  16.308 +
  16.309 +            @Override
  16.310 +            protected Node leaveDefault(final Node node) {
  16.311 +                return ensureUniqueLabels(node);
  16.312 +            };
  16.313 +
  16.314 +            private Node ensureUniqueLabels(final Node node) {
  16.315 +                // If we're returning a cached AST, we must also ensure unique labels
  16.316 +                return cached ? node.ensureUniqueLabels(lc) : node;
  16.317 +            }
  16.318 +        });
  16.319 +
  16.320 +        if (blockDefinedSymbols != null) {
  16.321 +            // Mark all symbols not defined in blocks as globals
  16.322 +            Block newBody = null;
  16.323 +            for(final Symbol symbol: symbolReplacements.values()) {
  16.324 +                if(!blockDefinedSymbols.contains(symbol)) {
  16.325 +                    assert symbol.isScope(); // must be scope
  16.326 +                    assert externalScopeDepths.containsKey(symbol.getName()); // must be known to us as an external
  16.327 +                    // Register it in the function body symbol table as a new global symbol
  16.328 +                    symbol.setFlags((symbol.getFlags() & ~Symbol.KINDMASK) | Symbol.IS_GLOBAL);
  16.329 +                    if (newBody == null) {
  16.330 +                        newBody = newFn.getBody().copyWithNewSymbols();
  16.331 +                        newFn = newFn.setBody(null, newBody);
  16.332 +                    }
  16.333 +                    assert newBody.getExistingSymbol(symbol.getName()) == null; // must not be defined in the body already
  16.334 +                    newBody.putSymbol(symbol);
  16.335 +                }
  16.336 +            }
  16.337 +        }
  16.338 +        return newFn.setCached(null);
  16.339 +    }
  16.340 +
  16.341      private boolean getFunctionFlag(final int flag) {
  16.342          return (functionFlags & flag) != 0;
  16.343      }
  16.344 @@ -512,9 +728,9 @@
  16.345          final FunctionNode fn = reparse();
  16.346          final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
  16.347          final FunctionNode compiledFn = compiler.compile(fn,
  16.348 -                isSerialized() ? CompilationPhases.COMPILE_ALL_SERIALIZED : CompilationPhases.COMPILE_ALL);
  16.349 +                fn.isCached() ? CompilationPhases.COMPILE_ALL_CACHED : CompilationPhases.COMPILE_ALL);
  16.350  
  16.351 -        if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
  16.352 +        if (persist && !compiledFn.hasApplyToCallSpecialization()) {
  16.353              compiler.persistClassInfo(cacheKey, compiledFn);
  16.354          }
  16.355          return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());

mercurial