Mon, 31 Aug 2015 15:18:59 +0200
8133300: Ensure symbol table immutability in Nashorn AST
Reviewed-by: hannesw, lagergren
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());