8133300: Ensure symbol table immutability in Nashorn AST
Reviewed-by: hannesw, lagergren
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java Mon Aug 31 15:18:59 2015 +0200
@@ -149,12 +149,14 @@
private final Deque<Set<String>> thisProperties = new ArrayDeque<>();
private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol
private final Compiler compiler;
+ private final boolean isOnDemand;
public AssignSymbols(final Compiler compiler) {
super(new LexicalContext());
this.compiler = compiler;
this.log = initLogger(compiler.getContext());
this.debug = log.isEnabled();
+ this.isOnDemand = compiler.isOnDemandCompilation();
}
@Override
@@ -390,7 +392,7 @@
// Create and add to appropriate block.
symbol = createSymbol(name, flags);
- symbolBlock.putSymbol(lc, symbol);
+ symbolBlock.putSymbol(symbol);
if ((flags & IS_SCOPE) == 0) {
// Initial assumption; symbol can lose its slot later
@@ -440,7 +442,7 @@
start(block);
if (lc.isFunctionBody()) {
- block.clearSymbols();
+ assert !block.hasSymbols();
final FunctionNode fn = lc.getCurrentFunction();
if (isUnparsedFunction(fn)) {
// It's a skipped nested function. Just mark the symbols being used by it as being in use.
@@ -459,7 +461,7 @@
}
private boolean isUnparsedFunction(final FunctionNode fn) {
- return compiler.isOnDemandCompilation() && fn != lc.getOutermostFunction();
+ return isOnDemand && fn != lc.getOutermostFunction();
}
@Override
@@ -747,28 +749,6 @@
}
}
- @Override
- public Node leaveBlock(final Block block) {
- // It's not necessary to guard the marking of symbols as locals with this "if" condition for
- // correctness, it's just an optimization -- runtime type calculation is not used when the compilation
- // is not an on-demand optimistic compilation, so we can skip locals marking then.
- if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) {
- // OTOH, we must not declare symbols from nested functions to be locals. As we're doing on-demand
- // compilation, and we're skipping parsing the function bodies for nested functions, this
- // basically only means their parameters. It'd be enough to mistakenly declare to be a local a
- // symbol in the outer function named the same as one of the parameters, though.
- if (lc.getFunction(block) == lc.getOutermostFunction()) {
- for (final Symbol symbol: block.getSymbols()) {
- if (!symbol.isScope()) {
- assert symbol.isVar() || symbol.isParam();
- compiler.declareLocalSymbol(symbol.getName());
- }
- }
- }
- }
- return block;
- }
-
private Node leaveDELETE(final UnaryNode unaryNode) {
final FunctionNode currentFunctionNode = lc.getCurrentFunction();
final boolean strictMode = currentFunctionNode.isStrict();
@@ -786,9 +766,9 @@
if (symbol.isThis()) {
// Can't delete "this", ignore and return true
- return LiteralNode.newInstance(unaryNode, true).accept(this);
+ return LiteralNode.newInstance(unaryNode, true);
}
- final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this);
+ final Expression literalNode = LiteralNode.newInstance(unaryNode, name);
final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel())));
if (!failDelete) {
@@ -799,7 +779,7 @@
if (failDelete) {
request = Request.FAIL_DELETE;
- } else if (symbol.isGlobal() && !symbol.isFunctionDeclaration()) {
+ } else if ((symbol.isGlobal() && !symbol.isFunctionDeclaration()) || symbol.isProgramLevel()) {
request = Request.SLOW_DELETE;
}
} else if (rhs instanceof AccessNode) {
@@ -807,7 +787,7 @@
final String property = ((AccessNode)rhs).getProperty();
args.add(base);
- args.add((Expression)LiteralNode.newInstance(unaryNode, property).accept(this));
+ args.add(LiteralNode.newInstance(unaryNode, property));
args.add(strictFlagNode);
} else if (rhs instanceof IndexNode) {
@@ -820,15 +800,15 @@
args.add(strictFlagNode);
} else {
- return LiteralNode.newInstance(unaryNode, true).accept(this);
+ return LiteralNode.newInstance(unaryNode, true);
}
- return new RuntimeNode(unaryNode, request, args).accept(this);
+ return new RuntimeNode(unaryNode, request, args);
}
@Override
public Node leaveForNode(final ForNode forNode) {
if (forNode.isForIn()) {
- forNode.setIterator(newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
+ return forNode.setIterator(lc, newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
}
return end(forNode);
@@ -904,19 +884,18 @@
public Node leaveSwitchNode(final SwitchNode switchNode) {
// We only need a symbol for the tag if it's not an integer switch node
if(!switchNode.isUniqueInteger()) {
- switchNode.setTag(newObjectInternal(SWITCH_TAG_PREFIX));
+ return switchNode.setTag(lc, newObjectInternal(SWITCH_TAG_PREFIX));
}
return switchNode;
}
@Override
public Node leaveTryNode(final TryNode tryNode) {
- tryNode.setException(exceptionSymbol());
assert tryNode.getFinallyBody() == null;
end(tryNode);
- return tryNode;
+ return tryNode.setException(lc, exceptionSymbol());
}
private Node leaveTYPEOF(final UnaryNode unaryNode) {
@@ -925,13 +904,13 @@
final List<Expression> args = new ArrayList<>();
if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) {
args.add(compilerConstantIdentifier(SCOPE));
- args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null
+ args.add(LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName())); //null
} else {
args.add(rhs);
- args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
+ args.add(LiteralNode.newInstance(unaryNode)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
}
- final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args).accept(this);
+ final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args);
end(unaryNode);
@@ -939,7 +918,7 @@
}
private FunctionNode markProgramBlock(final FunctionNode functionNode) {
- if (compiler.isOnDemandCompilation() || !functionNode.isProgram()) {
+ if (isOnDemand || !functionNode.isProgram()) {
return functionNode;
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AstSerializer.java Mon Aug 31 17:51:02 2015 +0530
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.nashorn.internal.codegen;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.util.Collections;
-import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
-import jdk.nashorn.internal.ir.Block;
-import jdk.nashorn.internal.ir.FunctionNode;
-import jdk.nashorn.internal.ir.LexicalContext;
-import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.Statement;
-import jdk.nashorn.internal.ir.visitor.NodeVisitor;
-import jdk.nashorn.internal.runtime.options.Options;
-
-/**
- * This static utility class performs serialization of FunctionNode ASTs to a byte array.
- * The format is a standard Java serialization stream, deflated.
- */
-final class AstSerializer {
- // Experimentally, we concluded that compression level 4 gives a good tradeoff between serialization speed
- // and size.
- private static final int COMPRESSION_LEVEL = Options.getIntProperty("nashorn.serialize.compression", 4);
- static byte[] serialize(final FunctionNode fn) {
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- final Deflater deflater = new Deflater(COMPRESSION_LEVEL);
- try (final ObjectOutputStream oout = new ObjectOutputStream(new DeflaterOutputStream(out, deflater))) {
- oout.writeObject(removeInnerFunctionBodies(fn));
- } catch (final IOException e) {
- throw new AssertionError("Unexpected exception serializing function", e);
- } finally {
- deflater.end();
- }
- return out.toByteArray();
- }
-
- private static FunctionNode removeInnerFunctionBodies(final FunctionNode fn) {
- return (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
- @Override
- public Node leaveBlock(final Block block) {
- if (lc.isFunctionBody() && lc.getFunction(block) != lc.getOutermostFunction()) {
- return block.setStatements(lc, Collections.<Statement>emptyList());
- }
- return super.leaveBlock(block);
- }
- });
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CacheAst.java Mon Aug 31 15:18:59 2015 +0200
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.codegen;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.LexicalContext;
+import jdk.nashorn.internal.ir.Node;
+import jdk.nashorn.internal.ir.Statement;
+import jdk.nashorn.internal.ir.visitor.NodeVisitor;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
+
+class CacheAst extends NodeVisitor<LexicalContext> {
+ private final Deque<RecompilableScriptFunctionData> dataStack = new ArrayDeque<>();
+
+ private final Compiler compiler;
+
+ CacheAst(final Compiler compiler) {
+ super(new LexicalContext());
+ this.compiler = compiler;
+ assert !compiler.isOnDemandCompilation();
+ }
+
+ @Override
+ public boolean enterFunctionNode(final FunctionNode functionNode) {
+ final int id = functionNode.getId();
+ // It isn't necessary to keep a stack of RecompilableScriptFunctionData, but then we'd need to do a
+ // potentially transitive lookup with compiler.getScriptFunctionData(id) for deeper functions; this way
+ // we keep it constant time.
+ dataStack.push(dataStack.isEmpty() ? compiler.getScriptFunctionData(id) : dataStack.peek().getScriptFunctionData(id));
+ return true;
+ }
+
+ @Override
+ public Node leaveFunctionNode(final FunctionNode functionNode) {
+ final RecompilableScriptFunctionData data = dataStack.pop();
+ if (functionNode.isSplit()) {
+ // NOTE: cache only split function ASTs from eager pass. Caching non-split functions would require
+ // some additional work, namely creating the concept of "uncacheable" function and reworking
+ // ApplySpecialization to ensure that functions undergoing apply-to-call transformations are not
+ // cacheable as well as recomputing Symbol.useCount when caching the eagerly parsed AST.
+ // Recomputing Symbol.useCount would be needed so it will only reflect uses from within the
+ // function being cached (and not reflect uses from its own nested functions or functions it is
+ // nested in). This is consistent with the count an on-demand recompilation of the function would
+ // produce. This is important as the decision to emit shared scope calls is based on this count,
+ // and if it is not matched between a previous version of the code and its deoptimizing rest-of
+ // compilation, it can result in rest-of not emitting a shared scope call where a previous version
+ // of the code (compiled from a cached eager pre-pass seeing higher (global) useCount) would emit
+ // it, causing a mismatch in stack shapes between previous code and its rest-of.
+ data.setCachedAst(functionNode);
+ }
+
+ if (!dataStack.isEmpty() && ((dataStack.peek().getFunctionFlags() & FunctionNode.IS_SPLIT) != 0)) {
+ // Return a function node with no body so that caching outer functions doesn't hold on to nested
+ // functions' bodies. Note we're doing this only for functions directly nested inside split
+ // functions, since we're only caching the split ones. It is not necessary to limit body removal
+ // to just these functions, but it's a cheap way to prevent unnecessary AST mutations.
+ return functionNode.setBody(lc, functionNode.getBody().setStatements(null, Collections.<Statement>emptyList()));
+ }
+ return functionNode;
+ }
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompilationPhase.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompilationPhase.java Mon Aug 31 15:18:59 2015 +0200
@@ -48,11 +48,13 @@
import java.util.Set;
import jdk.nashorn.internal.AssertsEnabled;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
+import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.Node;
+import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.debug.ASTWriter;
import jdk.nashorn.internal.ir.debug.PrintVisitor;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@@ -189,7 +191,7 @@
}
},
- SERIALIZE_SPLIT_PHASE(
+ CACHE_AST(
EnumSet.of(
INITIALIZED,
PARSED,
@@ -199,20 +201,21 @@
SPLIT)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
- return transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
- @Override
- public boolean enterFunctionNode(final FunctionNode functionNode) {
- if (functionNode.isSplit()) {
- compiler.serializeAst(functionNode);
- }
- return true;
- }
- });
+ if (!compiler.isOnDemandCompilation()) {
+ // Only do this on initial preprocessing of the source code. For on-demand compilations from
+ // source, FindScopeDepths#leaveFunctionNode() calls data.setCachedAst() for the sole function
+ // being compiled.
+ transformFunction(fn, new CacheAst(compiler));
+ }
+ // NOTE: we're returning the original fn as we have destructively modified the cached functions by
+ // removing their bodies. This step is associating FunctionNode objects with
+ // RecompilableScriptFunctionData; it's not really modifying the AST.
+ return fn;
}
@Override
public String toString() {
- return "'Serialize Split Functions'";
+ return "'Cache ASTs'";
}
},
@@ -255,6 +258,51 @@
}
},
+ DECLARE_LOCAL_SYMBOLS_TO_COMPILER(
+ EnumSet.of(
+ INITIALIZED,
+ PARSED,
+ CONSTANT_FOLDED,
+ LOWERED,
+ BUILTINS_TRANSFORMED,
+ SPLIT,
+ SYMBOLS_ASSIGNED,
+ SCOPE_DEPTHS_COMPUTED)) {
+ @Override
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ // It's not necessary to guard the marking of symbols as locals with this "if" condition for
+ // correctness, it's just an optimization -- runtime type calculation is not used when the compilation
+ // is not an on-demand optimistic compilation, so we can skip locals marking then.
+ if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) {
+ fn.getBody().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+ @Override
+ public boolean enterFunctionNode(final FunctionNode functionNode) {
+ // OTOH, we must not declare symbols from nested functions to be locals. As we're doing on-demand
+ // compilation, and we're skipping parsing the function bodies for nested functions, this
+ // basically only means their parameters. It'd be enough to mistakenly declare to be a local a
+ // symbol in the outer function named the same as one of the parameters, though.
+ return false;
+ };
+ @Override
+ public boolean enterBlock(final Block block) {
+ for (final Symbol symbol: block.getSymbols()) {
+ if (!symbol.isScope()) {
+ compiler.declareLocalSymbol(symbol.getName());
+ }
+ }
+ return true;
+ };
+ });
+ }
+ return fn;
+ }
+
+ @Override
+ public String toString() {
+ return "'Local Symbols Declaration'";
+ }
+ },
+
OPTIMISTIC_TYPE_ASSIGNMENT_PHASE(
EnumSet.of(
INITIALIZED,
@@ -382,7 +430,7 @@
}
},
- REINITIALIZE_SERIALIZED(
+ REINITIALIZE_CACHED(
EnumSet.of(
INITIALIZED,
PARSED,
@@ -430,7 +478,7 @@
@Override
public String toString() {
- return "'Deserialize'";
+ return "'Reinitialize cached'";
}
},
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java Mon Aug 31 15:18:59 2015 +0200
@@ -160,42 +160,41 @@
*/
private static final int COMPILE_UNIT_NAME_BUFFER_SIZE = 32;
- private final Map<Integer, byte[]> serializedAsts = new HashMap<>();
-
/**
* Compilation phases that a compilation goes through
*/
public static class CompilationPhases implements Iterable<CompilationPhase> {
/**
- * Singleton that describes compilation up to the phase where a function can be serialized.
+ * Singleton that describes compilation up to the phase where a function can be cached.
*/
- private final static CompilationPhases COMPILE_UPTO_SERIALIZABLE = new CompilationPhases(
+ private final static CompilationPhases COMPILE_UPTO_CACHED = new CompilationPhases(
"Common initial phases",
CompilationPhase.CONSTANT_FOLDING_PHASE,
CompilationPhase.LOWERING_PHASE,
CompilationPhase.TRANSFORM_BUILTINS_PHASE,
CompilationPhase.SPLITTING_PHASE,
CompilationPhase.PROGRAM_POINT_PHASE,
- CompilationPhase.SERIALIZE_SPLIT_PHASE
+ CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
+ CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
+ CompilationPhase.CACHE_AST
);
- private final static CompilationPhases COMPILE_SERIALIZABLE_UPTO_BYTECODE = new CompilationPhases(
+ private final static CompilationPhases COMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
"After common phases, before bytecode generator",
- CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
- CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
+ CompilationPhase.DECLARE_LOCAL_SYMBOLS_TO_COMPILER,
CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE
);
/**
- * Singleton that describes additional steps to be taken after deserializing, all the way up to (but not
- * including) generating and installing code.
+ * Singleton that describes additional steps to be taken after retrieving a cached function, all the
+ * way up to (but not including) generating and installing code.
*/
- public final static CompilationPhases RECOMPILE_SERIALIZED_UPTO_BYTECODE = new CompilationPhases(
- "Recompile serialized function up to bytecode",
- CompilationPhase.REINITIALIZE_SERIALIZED,
- COMPILE_SERIALIZABLE_UPTO_BYTECODE
+ public final static CompilationPhases RECOMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
+ "Recompile cached function up to bytecode",
+ CompilationPhase.REINITIALIZE_CACHED,
+ COMPILE_CACHED_UPTO_BYTECODE
);
/**
@@ -211,8 +210,8 @@
/** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */
public final static CompilationPhases COMPILE_UPTO_BYTECODE = new CompilationPhases(
"Compile upto bytecode",
- COMPILE_UPTO_SERIALIZABLE,
- COMPILE_SERIALIZABLE_UPTO_BYTECODE);
+ COMPILE_UPTO_CACHED,
+ COMPILE_CACHED_UPTO_BYTECODE);
/** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */
public final static CompilationPhases COMPILE_ALL_NO_INSTALL = new CompilationPhases(
@@ -227,9 +226,9 @@
GENERATE_BYTECODE_AND_INSTALL);
/** Singleton that describes a full compilation - this includes code installation - from serialized state*/
- public final static CompilationPhases COMPILE_ALL_SERIALIZED = new CompilationPhases(
+ public final static CompilationPhases COMPILE_ALL_CACHED = new CompilationPhases(
"Eager compilation from serializaed state",
- RECOMPILE_SERIALIZED_UPTO_BYTECODE,
+ RECOMPILE_CACHED_UPTO_BYTECODE,
GENERATE_BYTECODE_AND_INSTALL);
/**
@@ -248,9 +247,9 @@
GENERATE_BYTECODE_AND_INSTALL_RESTOF);
/** Compile from serialized for a rest of method */
- public final static CompilationPhases COMPILE_SERIALIZED_RESTOF = new CompilationPhases(
+ public final static CompilationPhases COMPILE_CACHED_RESTOF = new CompilationPhases(
"Compile serialized, rest of",
- RECOMPILE_SERIALIZED_UPTO_BYTECODE,
+ RECOMPILE_CACHED_UPTO_BYTECODE,
GENERATE_BYTECODE_AND_INSTALL_RESTOF);
private final List<CompilationPhase> phases;
@@ -313,7 +312,7 @@
}
boolean isRestOfCompilation() {
- return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_SERIALIZED_RESTOF;
+ return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_CACHED_RESTOF;
}
String getDesc() {
@@ -766,14 +765,6 @@
compileUnits.addAll(newUnits);
}
- void serializeAst(final FunctionNode fn) {
- serializedAsts.put(fn.getId(), AstSerializer.serialize(fn));
- }
-
- byte[] removeSerializedAst(final int fnId) {
- return serializedAsts.remove(fnId);
- }
-
CompileUnit findUnit(final long weight) {
for (final CompileUnit unit : compileUnits) {
if (unit.canHold(weight)) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java Mon Aug 31 15:18:59 2015 +0200
@@ -188,6 +188,9 @@
log.fine("Reviving scriptfunction ", quote(name), " as defined in previous (now lost) dynamic scope.");
newFunctionNode = newFunctionNode.setInDynamicContext(lc);
}
+ if (newFunctionNode == lc.getOutermostFunction() && !newFunctionNode.hasApplyToCallSpecialization()) {
+ data.setCachedAst(newFunctionNode);
+ }
return newFunctionNode;
}
@@ -208,8 +211,7 @@
ObjectClassGenerator.createAllocationStrategy(newFunctionNode.getThisProperties(), compiler.getContext().useDualFields()),
nestedFunctions,
externalSymbolDepths.get(fnId),
- internalSymbols.get(fnId),
- compiler.removeSerializedAst(fnId));
+ internalSymbols.get(fnId));
if (lc.getOutermostFunction() != newFunctionNode) {
final FunctionNode parentFn = lc.getParentFunction(newFunctionNode);
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Label.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Label.java Mon Aug 31 15:18:59 2015 +0200
@@ -497,7 +497,7 @@
private transient Label.Stack stack;
/** ASM representation of this label */
- private jdk.internal.org.objectweb.asm.Label label;
+ private transient jdk.internal.org.objectweb.asm.Label label;
/** Id for debugging purposes, remove if footprint becomes unmanageable */
private final int id;
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Block.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Block.java Mon Aug 31 15:18:59 2015 +0200
@@ -159,11 +159,42 @@
}
/**
- * Clear the symbols in the block.
- * TODO: make this immutable.
+ * Returns true if this block defines any symbols.
+ * @return true if this block defines any symbols.
+ */
+ public boolean hasSymbols() {
+ return !symbols.isEmpty();
+ }
+
+ /**
+ * Replaces symbols defined in this block with different symbols. Used to ensure symbol tables are
+ * immutable upon construction and have copy-on-write semantics. Note that this method only replaces the
+ * symbols in the symbol table, it does not act on any contained AST nodes that might reference the symbols.
+ * Those should be updated separately as this method is meant to be used as part of such an update pass.
+ * @param lc the current lexical context
+ * @param replacements the map of symbol replacements
+ * @return a new block with replaced symbols, or this block if none of the replacements modified the symbol
+ * table.
*/
- public void clearSymbols() {
- symbols.clear();
+ public Block replaceSymbols(final LexicalContext lc, final Map<Symbol, Symbol> replacements) {
+ if (symbols.isEmpty()) {
+ return this;
+ }
+ final LinkedHashMap<String, Symbol> newSymbols = new LinkedHashMap<>(symbols);
+ for (final Map.Entry<String, Symbol> entry: newSymbols.entrySet()) {
+ final Symbol newSymbol = replacements.get(entry.getValue());
+ assert newSymbol != null : "Missing replacement for " + entry.getKey();
+ entry.setValue(newSymbol);
+ }
+ return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, newSymbols, conversion));
+ }
+
+ /**
+ * Returns a copy of this block with a shallow copy of the symbol table.
+ * @return a copy of this block with a shallow copy of the symbol table.
+ */
+ public Block copyWithNewSymbols() {
+ return new Block(this, finish, statements, flags, new LinkedHashMap<>(symbols), conversion);
}
@Override
@@ -191,7 +222,7 @@
* @return symbol iterator
*/
public List<Symbol> getSymbols() {
- return Collections.unmodifiableList(new ArrayList<>(symbols.values()));
+ return symbols.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(symbols.values()));
}
/**
@@ -355,10 +386,9 @@
/**
* Add or overwrite an existing symbol in the block
*
- * @param lc get lexical context
* @param symbol symbol
*/
- public void putSymbol(final LexicalContext lc, final Symbol symbol) {
+ public void putSymbol(final Symbol symbol) {
symbols.put(symbol.getName(), symbol);
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java Mon Aug 31 15:18:59 2015 +0200
@@ -43,7 +43,7 @@
private final JoinPredecessorExpression modify;
/** Iterator symbol. */
- private Symbol iterator;
+ private final Symbol iterator;
/** Is this a normal for in loop? */
public static final int IS_FOR_IN = 1 << 0;
@@ -86,23 +86,23 @@
this.flags = flags;
this.init = init;
this.modify = modify;
+ this.iterator = null;
}
private ForNode(final ForNode forNode, final Expression init, final JoinPredecessorExpression test,
- final Block body, final JoinPredecessorExpression modify, final int flags, final boolean controlFlowEscapes, final LocalVariableConversion conversion) {
+ final Block body, final JoinPredecessorExpression modify, final int flags,
+ final boolean controlFlowEscapes, final LocalVariableConversion conversion, final Symbol iterator) {
super(forNode, test, body, controlFlowEscapes, conversion);
this.init = init;
this.modify = modify;
this.flags = flags;
- // Even if the for node gets cloned in try/finally, the symbol can be shared as only one branch of the finally
- // is executed.
- this.iterator = forNode.iterator;
+ this.iterator = iterator;
}
@Override
public Node ensureUniqueLabels(final LexicalContext lc) {
- return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
+ return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
}
@Override
@@ -175,7 +175,7 @@
if (this.init == init) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
+ return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
}
/**
@@ -204,10 +204,15 @@
/**
* Assign an iterator symbol to this ForNode. Used for for in and for each constructs
+ * @param lc the current lexical context
* @param iterator the iterator symbol
+ * @return a ForNode with the iterator set
*/
- public void setIterator(final Symbol iterator) {
- this.iterator = iterator;
+ public ForNode setIterator(final LexicalContext lc, final Symbol iterator) {
+ if (this.iterator == iterator) {
+ return this;
+ }
+ return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
}
/**
@@ -228,7 +233,7 @@
if (this.modify == modify) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
+ return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
}
@Override
@@ -236,7 +241,7 @@
if (this.test == test) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
+ return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
}
@Override
@@ -249,7 +254,7 @@
if (this.body == body) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
+ return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
}
@Override
@@ -257,12 +262,12 @@
if (this.controlFlowEscapes == controlFlowEscapes) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
+ return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
}
@Override
JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
- return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
+ return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion, iterator));
}
@Override
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/FunctionNode.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/FunctionNode.java Mon Aug 31 15:18:59 2015 +0200
@@ -264,6 +264,11 @@
*/
public static final int NEEDS_CALLEE = 1 << 26;
+ /**
+ * Is the function node cached?
+ */
+ public static final int IS_CACHED = 1 << 27;
+
/** extension callsite flags mask */
public static final int EXTENSION_CALLSITE_FLAGS = IS_PRINT_PARSE |
IS_PRINT_LOWER_PARSE | IS_PRINT_AST | IS_PRINT_LOWER_AST |
@@ -353,7 +358,7 @@
final List<IdentNode> parameters,
final int thisProperties,
final Class<?> rootClass,
- final Source source, Namespace namespace) {
+ final Source source, final Namespace namespace) {
super(functionNode);
this.endParserState = endParserState;
@@ -757,7 +762,7 @@
*/
public boolean needsCallee() {
// NOTE: we only need isSplit() here to ensure that :scope can never drop below slot 2 for splitting array units.
- return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict()) || hasOptimisticApplyToCall();
+ return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict()) || hasApplyToCallSpecialization();
}
/**
@@ -774,7 +779,7 @@
* Return true if function contains an apply to call transform
* @return true if this function has transformed apply to call
*/
- public boolean hasOptimisticApplyToCall() {
+ public boolean hasApplyToCallSpecialization() {
return getFlag(HAS_APPLY_TO_CALL_SPECIALIZATION);
}
@@ -1170,6 +1175,24 @@
}
/**
+ * Returns true if this function node has been cached.
+ * @return true if this function node has been cached.
+ */
+ public boolean isCached() {
+ return getFlag(IS_CACHED);
+ }
+
+ /**
+ * Mark this function node as having been cached.
+ * @param lc the current lexical context
+ * @return a function node equivalent to this one, with the flag set.
+ */
+ public FunctionNode setCached(final LexicalContext lc) {
+ return setFlag(lc, IS_CACHED);
+ }
+
+
+ /**
* Get the compile unit used to compile this function
* @see Compiler
* @return the compile unit
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/SwitchNode.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/SwitchNode.java Mon Aug 31 15:18:59 2015 +0200
@@ -53,7 +53,7 @@
private final boolean uniqueInteger;
/** Tag symbol. */
- private Symbol tag;
+ private final Symbol tag;
/**
* Constructor
@@ -71,15 +71,16 @@
this.cases = cases;
this.defaultCaseIndex = defaultCase == null ? -1 : cases.indexOf(defaultCase);
this.uniqueInteger = false;
+ this.tag = null;
}
private SwitchNode(final SwitchNode switchNode, final Expression expression, final List<CaseNode> cases,
- final int defaultCaseIndex, final LocalVariableConversion conversion, final boolean uniqueInteger) {
+ final int defaultCaseIndex, final LocalVariableConversion conversion, final boolean uniqueInteger, final Symbol tag) {
super(switchNode, conversion);
this.expression = expression;
this.cases = cases;
this.defaultCaseIndex = defaultCaseIndex;
- this.tag = switchNode.getTag(); //TODO are symbols inherited as references?
+ this.tag = tag;
this.uniqueInteger = uniqueInteger;
}
@@ -89,7 +90,7 @@
for (final CaseNode caseNode : cases) {
newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody(), caseNode.getLocalVariableConversion()));
}
- return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion, uniqueInteger));
+ return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion, uniqueInteger, tag));
}
@Override
@@ -157,7 +158,7 @@
if (this.cases == cases) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
+ return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
}
/**
@@ -189,7 +190,7 @@
if (this.expression == expression) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
+ return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
}
/**
@@ -204,10 +205,15 @@
/**
* Set the tag symbol for this switch. The tag symbol is where
* the switch expression result is stored
+ * @param lc lexical context
* @param tag a symbol
+ * @return a switch node with the symbol set
*/
- public void setTag(final Symbol tag) {
- this.tag = tag;
+ public SwitchNode setTag(final LexicalContext lc, final Symbol tag) {
+ if (this.tag == tag) {
+ return this;
+ }
+ return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
}
/**
@@ -229,12 +235,12 @@
if(this.uniqueInteger == uniqueInteger) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
+ return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
}
@Override
JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
- return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
+ return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
}
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Symbol.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Symbol.java Mon Aug 31 15:18:59 2015 +0200
@@ -25,7 +25,10 @@
package jdk.nashorn.internal.ir;
+import java.io.IOException;
+import java.io.ObjectInputStream;
import java.io.PrintWriter;
+import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
@@ -47,7 +50,9 @@
* refer to their location.
*/
-public final class Symbol implements Comparable<Symbol> {
+public final class Symbol implements Comparable<Symbol>, Cloneable, Serializable {
+ private static final long serialVersionUID = 1L;
+
/** Is this Global */
public static final int IS_GLOBAL = 1;
/** Is this a variable */
@@ -94,10 +99,10 @@
/** First bytecode method local variable slot for storing the value(s) of this variable. -1 indicates the variable
* is not stored in local variable slots or it is not yet known. */
- private int firstSlot = -1;
+ private transient int firstSlot = -1;
/** Field number in scope or property; array index in varargs when not using arguments object. */
- private int fieldIndex = -1;
+ private transient int fieldIndex = -1;
/** Number of times this symbol is used in code */
private int useCount;
@@ -144,6 +149,15 @@
}
}
+ @Override
+ public Symbol clone() {
+ try {
+ return (Symbol)super.clone();
+ } catch (final CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+
private static String align(final String string, final int max) {
final StringBuilder sb = new StringBuilder();
sb.append(string.substring(0, Math.min(string.length(), max)));
@@ -337,7 +351,7 @@
* Flag this symbol as scope as described in {@link Symbol#isScope()}
* @return the symbol
*/
- public Symbol setIsScope() {
+ public Symbol setIsScope() {
if (!isScope()) {
if(shouldTrace()) {
trace("SET IS SCOPE");
@@ -609,11 +623,11 @@
/**
* Increase the symbol's use count by one.
- * @return the symbol
*/
- public Symbol increaseUseCount() {
- useCount++;
- return this;
+ public void increaseUseCount() {
+ if (isScope()) { // Avoid dirtying a cache line; we only need the use count for scoped symbols
+ useCount++;
+ }
}
/**
@@ -669,4 +683,10 @@
new Throwable().printStackTrace(Context.getCurrentErr());
}
}
+
+ private void readObject(final ObjectInputStream in) throws ClassNotFoundException, IOException {
+ in.defaultReadObject();
+ firstSlot = -1;
+ fieldIndex = -1;
+ }
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/TryNode.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/TryNode.java Mon Aug 31 15:18:59 2015 +0200
@@ -65,7 +65,7 @@
private final List<Block> inlinedFinallies;
/** Exception symbol. */
- private Symbol exception;
+ private final Symbol exception;
private final LocalVariableConversion conversion;
@@ -86,22 +86,23 @@
this.finallyBody = finallyBody;
this.conversion = null;
this.inlinedFinallies = Collections.emptyList();
+ this.exception = null;
}
- private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies) {
+ 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) {
super(tryNode);
this.body = body;
this.catchBlocks = catchBlocks;
this.finallyBody = finallyBody;
this.conversion = conversion;
this.inlinedFinallies = inlinedFinallies;
- this.exception = tryNode.exception;
+ this.exception = exception;
}
@Override
public Node ensureUniqueLabels(final LexicalContext lc) {
//try nodes are never in lex context
- return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
+ return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception);
}
@Override
@@ -160,7 +161,7 @@
if (this.body == body) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
+ return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
}
/**
@@ -197,7 +198,7 @@
if (this.catchBlocks == catchBlocks) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
+ return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
}
/**
@@ -209,12 +210,15 @@
}
/**
* Set the exception symbol for this try block
+ * @param lc lexical context
* @param exception a symbol for the compiler to store the exception in
* @return new TryNode or same if unchanged
*/
- public TryNode setException(final Symbol exception) {
- this.exception = exception;
- return this;
+ public TryNode setException(final LexicalContext lc, final Symbol exception) {
+ if (this.exception == exception) {
+ return this;
+ }
+ return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
}
/**
@@ -277,7 +281,7 @@
if (this.finallyBody == finallyBody) {
return this;
}
- return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
+ return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
}
/**
@@ -293,7 +297,7 @@
return this;
}
assert checkInlinedFinallies(inlinedFinallies);
- return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
+ return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
}
private static boolean checkInlinedFinallies(final List<Block> inlinedFinallies) {
@@ -314,7 +318,7 @@
if(this.conversion == conversion) {
return this;
}
- return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
+ return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception);
}
@Override
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AstSerializer.java Mon Aug 31 15:18:59 2015 +0200
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.nashorn.internal.runtime;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.runtime.options.Options;
+
+/**
+ * This static utility class performs serialization of FunctionNode ASTs to a byte array.
+ * The format is a standard Java serialization stream, deflated.
+ */
+final class AstSerializer {
+ // Experimentally, we concluded that compression level 4 gives a good tradeoff between serialization speed
+ // and size.
+ private static final int COMPRESSION_LEVEL = Options.getIntProperty("nashorn.serialize.compression", 4);
+ static byte[] serialize(final FunctionNode fn) {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ final Deflater deflater = new Deflater(COMPRESSION_LEVEL);
+ try (final ObjectOutputStream oout = new ObjectOutputStream(new DeflaterOutputStream(out, deflater))) {
+ oout.writeObject(fn);
+ } catch (final IOException e) {
+ throw new AssertionError("Unexpected exception serializing function", e);
+ } finally {
+ deflater.end();
+ }
+ return out.toByteArray();
+ }
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CompiledFunction.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CompiledFunction.java Mon Aug 31 15:18:59 2015 +0200
@@ -27,6 +27,7 @@
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
+
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -820,7 +821,7 @@
// isn't available, we'll use the old one bound into the call site.
final OptimismInfo effectiveOptInfo = currentOptInfo != null ? currentOptInfo : oldOptInfo;
FunctionNode fn = effectiveOptInfo.reparse();
- final boolean serialized = effectiveOptInfo.isSerialized();
+ final boolean cached = fn.isCached();
final Compiler compiler = effectiveOptInfo.getCompiler(fn, ct, re); //set to non rest-of
if (!shouldRecompile) {
@@ -828,11 +829,11 @@
// recompiled a deoptimized version for an inner invocation.
// We still need to do the rest of from the beginning
logRecompile("Rest-of compilation [STANDALONE] ", fn, ct, effectiveOptInfo.invalidatedProgramPoints);
- return restOfHandle(effectiveOptInfo, compiler.compile(fn, serialized ? CompilationPhases.COMPILE_SERIALIZED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
+ return restOfHandle(effectiveOptInfo, compiler.compile(fn, cached ? CompilationPhases.COMPILE_CACHED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
}
logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, ct, effectiveOptInfo.invalidatedProgramPoints);
- fn = compiler.compile(fn, serialized ? CompilationPhases.RECOMPILE_SERIALIZED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE);
+ fn = compiler.compile(fn, cached ? CompilationPhases.RECOMPILE_CACHED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE);
log.fine("Reusable IR generated");
// compile the rest of the function, and install it
@@ -956,10 +957,6 @@
FunctionNode reparse() {
return data.reparse();
}
-
- boolean isSerialized() {
- return data.isSerialized();
- }
}
@SuppressWarnings("unused")
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon Aug 31 17:51:02 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon Aug 31 15:18:59 2015 +0200
@@ -26,16 +26,24 @@
package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.lookup.Lookup.MH;
+
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import jdk.internal.dynalink.support.NameCodec;
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
@@ -45,8 +53,15 @@
import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
import jdk.nashorn.internal.codegen.TypeMap;
import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.Block;
+import jdk.nashorn.internal.ir.ForNode;
import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.LexicalContext;
+import jdk.nashorn.internal.ir.Node;
+import jdk.nashorn.internal.ir.SwitchNode;
+import jdk.nashorn.internal.ir.Symbol;
+import jdk.nashorn.internal.ir.TryNode;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.objects.Global;
import jdk.nashorn.internal.parser.Parser;
@@ -55,6 +70,7 @@
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import jdk.nashorn.internal.runtime.logging.Loggable;
import jdk.nashorn.internal.runtime.logging.Logger;
+import jdk.nashorn.internal.runtime.options.Options;
/**
* This is a subclass that represents a script function that may be regenerated,
* for example with specialization based on call site types, or lazily generated.
@@ -66,6 +82,8 @@
/** Prefix used for all recompiled script classes */
public static final String RECOMPILATION_PREFIX = "Recompilation$";
+ private static final ExecutorService astSerializerExecutorService = createAstSerializerExecutorService();
+
/** Unique function node id for this function node */
private final int functionNodeId;
@@ -77,8 +95,12 @@
/** Source from which FunctionNode was parsed. */
private transient Source source;
- /** Serialized, compressed form of the AST. Used by split functions as they can't be reparsed from source. */
- private final byte[] serializedAst;
+ /**
+ * Cached form of the AST. Either a {@code SerializedAst} object used by split functions as they can't be
+ * reparsed from source, or a soft reference to a {@code FunctionNode} for other functions (it is safe
+ * to be cleared as they can be reparsed).
+ */
+ private volatile Object cachedAst;
/** Token of this function within the source. */
private final long token;
@@ -128,7 +150,6 @@
* @param nestedFunctions nested function map
* @param externalScopeDepths external scope depths
* @param internalSymbols internal symbols to method, defined in its scope
- * @param serializedAst a serialized AST representation. Normally only used for split functions.
*/
public RecompilableScriptFunctionData(
final FunctionNode functionNode,
@@ -136,8 +157,7 @@
final AllocationStrategy allocationStrategy,
final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
final Map<String, Integer> externalScopeDepths,
- final Set<String> internalSymbols,
- final byte[] serializedAst) {
+ final Set<String> internalSymbols) {
super(functionName(functionNode),
Math.min(functionNode.getParameters().size(), MAX_ARITY),
@@ -161,7 +181,6 @@
nfn.setParent(this);
}
- this.serializedAst = serializedAst;
createLogger();
}
@@ -244,7 +263,7 @@
* @return parent data, or null if non exists and also null IF UNKNOWN.
*/
public RecompilableScriptFunctionData getParent() {
- return parent;
+ return parent;
}
void setParent(final RecompilableScriptFunctionData parent) {
@@ -358,13 +377,11 @@
return allocationStrategy.allocate(map);
}
- boolean isSerialized() {
- return serializedAst != null;
- }
-
FunctionNode reparse() {
- if (isSerialized()) {
- return deserialize();
+ final FunctionNode cachedFunction = getCachedAst();
+ if (cachedFunction != null) {
+ assert cachedFunction.isCached();
+ return cachedFunction;
}
final int descPosition = Token.descPosition(token);
@@ -391,7 +408,98 @@
return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
}
- private FunctionNode deserialize() {
+ private FunctionNode getCachedAst() {
+ final Object lCachedAst = cachedAst;
+ // Are we softly caching the AST?
+ if (lCachedAst instanceof Reference<?>) {
+ final FunctionNode fn = (FunctionNode)((Reference<?>)lCachedAst).get();
+ if (fn != null) {
+ // Yes we are - this is fast
+ return cloneSymbols(fn);
+ }
+ // Are we strongly caching a serialized AST (for split functions only)?
+ } else if (lCachedAst instanceof SerializedAst) {
+ final SerializedAst serializedAst = (SerializedAst)lCachedAst;
+ // Even so, are we also softly caching the AST?
+ final FunctionNode cachedFn = serializedAst.cachedAst.get();
+ if (cachedFn != null) {
+ // Yes we are - this is fast
+ return cloneSymbols(cachedFn);
+ }
+ final FunctionNode deserializedFn = deserialize(serializedAst.serializedAst);
+ // Softly cache after deserialization, maybe next time we won't need to deserialize
+ serializedAst.cachedAst = new SoftReference<>(deserializedFn);
+ return deserializedFn;
+ }
+ // No cached representation; return null for reparsing
+ return null;
+ }
+
+ /**
+ * Sets the AST to cache in this function
+ * @param astToCache the new AST to cache
+ */
+ public void setCachedAst(final FunctionNode astToCache) {
+ assert astToCache.getId() == functionNodeId; // same function
+ assert !(cachedAst instanceof SerializedAst); // Can't overwrite serialized AST
+
+ final boolean isSplit = astToCache.isSplit();
+ // If we're caching a split function, we're doing it in the eager pass, hence there can be no other
+ // cached representation already. In other words, isSplit implies cachedAst == null.
+ assert !isSplit || cachedAst == null; //
+
+ final FunctionNode symbolClonedAst = cloneSymbols(astToCache);
+ final Reference<FunctionNode> ref = new SoftReference<>(symbolClonedAst);
+ cachedAst = ref;
+
+ // Asynchronously serialize split functions.
+ if (isSplit) {
+ astSerializerExecutorService.execute(() -> {
+ cachedAst = new SerializedAst(symbolClonedAst, ref);
+ });
+ }
+ }
+
+ /**
+ * Creates the AST serializer executor service used for in-memory serialization of split functions' ASTs.
+ * It is created with an unbounded queue (so it can queue any number of pending tasks). Its core and max
+ * threads is the same, but they are all allowed to time out so when there's no work, they can all go
+ * away. The threads will be daemons, and they will time out if idle for a minute. Their priority is also
+ * slightly lower than normal priority as we'd prefer the CPU to keep running the program; serializing
+ * split function is a memory conservation measure (it allows us to release the AST), it can wait a bit.
+ * @return an executor service with above described characteristics.
+ */
+ private static ExecutorService createAstSerializerExecutorService() {
+ final int threads = Math.max(1, Options.getIntProperty("nashorn.serialize.threads", Runtime.getRuntime().availableProcessors() / 2));
+ final ThreadPoolExecutor service = new ThreadPoolExecutor(threads, threads, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(),
+ (r) -> {
+ final Thread t = new Thread(r, "Nashorn AST Serializer");
+ t.setDaemon(true);
+ t.setPriority(Thread.NORM_PRIORITY - 1);
+ return t;
+ });
+ service.allowCoreThreadTimeOut(true);
+ return service;
+ }
+
+ /**
+ * A tuple of a serialized AST and a soft reference to a deserialized AST. This is used to cache split
+ * functions. Since split functions are altered from their source form, they can't be reparsed from
+ * source. While we could just use the {@code byte[]} representation in {@link RecompilableScriptFunctionData#cachedAst}
+ * we're using this tuple instead to also keep a deserialized AST around in memory to cut down on
+ * deserialization costs.
+ */
+ private static class SerializedAst {
+ private final byte[] serializedAst;
+ private volatile Reference<FunctionNode> cachedAst;
+
+ SerializedAst(final FunctionNode fn, final Reference<FunctionNode> cachedAst) {
+ this.serializedAst = AstSerializer.serialize(fn);
+ this.cachedAst = cachedAst;
+ }
+ }
+
+ private FunctionNode deserialize(final byte[] serializedAst) {
final ScriptEnvironment env = installer.getOwner();
final Timing timing = env._timing;
final long t1 = System.nanoTime();
@@ -402,6 +510,107 @@
}
}
+ private FunctionNode cloneSymbols(final FunctionNode fn) {
+ final IdentityHashMap<Symbol, Symbol> symbolReplacements = new IdentityHashMap<>();
+ final boolean cached = fn.isCached();
+ // blockDefinedSymbols is used to re-mark symbols defined outside the function as global. We only
+ // need to do this when we cache an eagerly parsed function (which currently means a split one, as we
+ // don't cache non-split functions from the eager pass); those already cached, or those not split
+ // don't need this step.
+ final Set<Symbol> blockDefinedSymbols = fn.isSplit() && !cached ? Collections.newSetFromMap(new IdentityHashMap<>()) : null;
+ FunctionNode newFn = (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+
+ private Symbol getReplacement(final Symbol original) {
+ if (original == null) {
+ return null;
+ }
+ final Symbol existingReplacement = symbolReplacements.get(original);
+ if (existingReplacement != null) {
+ return existingReplacement;
+ }
+ final Symbol newReplacement = original.clone();
+ symbolReplacements.put(original, newReplacement);
+ return newReplacement;
+ }
+
+ @Override
+ public Node leaveIdentNode(final IdentNode identNode) {
+ final Symbol oldSymbol = identNode.getSymbol();
+ if (oldSymbol != null) {
+ final Symbol replacement = getReplacement(oldSymbol);
+ return identNode.setSymbol(replacement);
+ }
+ return identNode;
+ }
+
+ @Override
+ public Node leaveForNode(final ForNode forNode) {
+ return ensureUniqueLabels(forNode.setIterator(lc, getReplacement(forNode.getIterator())));
+ }
+
+ @Override
+ public Node leaveSwitchNode(final SwitchNode switchNode) {
+ return ensureUniqueLabels(switchNode.setTag(lc, getReplacement(switchNode.getTag())));
+ }
+
+ @Override
+ public Node leaveTryNode(final TryNode tryNode) {
+ return ensureUniqueLabels(tryNode.setException(lc, getReplacement(tryNode.getException())));
+ }
+
+ @Override
+ public boolean enterBlock(final Block block) {
+ for(final Symbol symbol: block.getSymbols()) {
+ final Symbol replacement = getReplacement(symbol);
+ if (blockDefinedSymbols != null) {
+ blockDefinedSymbols.add(replacement);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Node leaveBlock(final Block block) {
+ return ensureUniqueLabels(block.replaceSymbols(lc, symbolReplacements));
+ }
+
+ @Override
+ public Node leaveFunctionNode(final FunctionNode functionNode) {
+ return functionNode.setParameters(lc, functionNode.visitParameters(this));
+ }
+
+ @Override
+ protected Node leaveDefault(final Node node) {
+ return ensureUniqueLabels(node);
+ };
+
+ private Node ensureUniqueLabels(final Node node) {
+ // If we're returning a cached AST, we must also ensure unique labels
+ return cached ? node.ensureUniqueLabels(lc) : node;
+ }
+ });
+
+ if (blockDefinedSymbols != null) {
+ // Mark all symbols not defined in blocks as globals
+ Block newBody = null;
+ for(final Symbol symbol: symbolReplacements.values()) {
+ if(!blockDefinedSymbols.contains(symbol)) {
+ assert symbol.isScope(); // must be scope
+ assert externalScopeDepths.containsKey(symbol.getName()); // must be known to us as an external
+ // Register it in the function body symbol table as a new global symbol
+ symbol.setFlags((symbol.getFlags() & ~Symbol.KINDMASK) | Symbol.IS_GLOBAL);
+ if (newBody == null) {
+ newBody = newFn.getBody().copyWithNewSymbols();
+ newFn = newFn.setBody(null, newBody);
+ }
+ assert newBody.getExistingSymbol(symbol.getName()) == null; // must not be defined in the body already
+ newBody.putSymbol(symbol);
+ }
+ }
+ }
+ return newFn.setCached(null);
+ }
+
private boolean getFunctionFlag(final int flag) {
return (functionFlags & flag) != 0;
}
@@ -512,9 +721,9 @@
final FunctionNode fn = reparse();
final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
final FunctionNode compiledFn = compiler.compile(fn,
- isSerialized() ? CompilationPhases.COMPILE_ALL_SERIALIZED : CompilationPhases.COMPILE_ALL);
+ fn.isCached() ? CompilationPhases.COMPILE_ALL_CACHED : CompilationPhases.COMPILE_ALL);
- if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
+ if (persist && !compiledFn.hasApplyToCallSpecialization()) {
compiler.persistClassInfo(cacheKey, compiledFn);
}
return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());