# HG changeset patch
# User lana
# Date 1404250048 25200
# Node ID eb2479579f1e0ebaa0dcba45ea301800fb44e00d
# Parent 28dd0c7beb3cad9cf95f17b4b5ad87eb447a4084# Parent b5a4e0ac31d183ffb7fbbb075874309cfd64c384
Merge
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/bin/jjsdebug.sh
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/bin/jjsdebug.sh Tue Jul 01 14:27:28 2014 -0700
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 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.
+#
+# 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.
+#
+
+$JAVA_HOME/bin/jjs -J-Djava.ext.dirs=`dirname $0`/../dist -J-agentlib:jdwp=transport=dt_socket,address=localhost:9009,server=y,suspend=y $*
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/make/build.xml
--- a/nashorn/make/build.xml Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/make/build.xml Tue Jul 01 14:27:28 2014 -0700
@@ -125,8 +125,7 @@
encoding="${javac.encoding}"
includeantruntime="false" fork="true">
-
-
+
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/make/nbproject/ide-targets.xml
--- a/nashorn/make/nbproject/ide-targets.xml Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/make/nbproject/ide-targets.xml Tue Jul 01 14:27:28 2014 -0700
@@ -34,7 +34,7 @@
-
+
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/make/project.properties
--- a/nashorn/make/project.properties Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/make/project.properties Tue Jul 01 14:27:28 2014 -0700
@@ -279,6 +279,7 @@
-Dfile.encoding=UTF-8 \
-Duser.language=${run.test.user.language} \
-Duser.country=${run.test.user.country} \
+ -Dnashorn.typeInfo.cacheDir=${build.dir}${file.separator}test${file.separator}type_info_cache \
${jfr.args} \
-XX:+HeapDumpOnOutOfMemoryError
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java
--- a/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java Tue Jul 01 14:27:28 2014 -0700
@@ -152,7 +152,7 @@
@SuppressWarnings("unused")
private MethodHandle selectMethod(final Object[] args) throws NoSuchMethodException {
- final Class>[] argTypes = new Class[args.length];
+ final Class>[] argTypes = new Class>[args.length];
for(int i = 0; i < argTypes.length; ++i) {
final Object arg = args[i];
argTypes[i] = arg == null ? ClassString.NULL_CLASS : arg.getClass();
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java
--- a/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java Tue Jul 01 14:27:28 2014 -0700
@@ -111,7 +111,7 @@
private final TypeBasedGuardingDynamicLinker[] linkers;
private final List[] singletonLinkers;
- @SuppressWarnings("unchecked")
+ @SuppressWarnings(value={"unchecked", "rawtypes"})
ClassToLinker(final TypeBasedGuardingDynamicLinker[] linkers) {
this.linkers = linkers;
singletonLinkers = new List[linkers.length];
@@ -120,6 +120,7 @@
}
}
+ @SuppressWarnings("fallthrough")
@Override
protected List computeValue(final Class> clazz) {
List list = NO_LINKER;
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java Tue Jul 01 14:27:28 2014 -0700
@@ -209,7 +209,7 @@
if (varNode.isFunctionDeclaration()) {
symbol.setIsFunctionDeclaration();
}
- return varNode.setName((IdentNode)ident.setSymbol(symbol));
+ return varNode.setName(ident.setSymbol(symbol));
}
return varNode;
}
@@ -217,7 +217,7 @@
}
private IdentNode compilerConstantIdentifier(final CompilerConstants cc) {
- return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc));
+ return createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc));
}
/**
@@ -263,7 +263,7 @@
final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName());
assert nameSymbol != null;
- return (VarNode)synthVar.setName((IdentNode)name.setSymbol(nameSymbol)).accept(this);
+ return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this);
}
private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) {
@@ -522,7 +522,7 @@
final Symbol paramSymbol = body.getExistingSymbol(param.getName());
assert paramSymbol != null;
assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags();
- newParams.add((IdentNode)param.setSymbol(paramSymbol));
+ newParams.add(param.setSymbol(paramSymbol));
// parameters should not be slots for a function that uses variable arity signature
if (isVarArg) {
@@ -702,7 +702,7 @@
// If this is a declared variable or a function parameter, delete always fails (except for globals).
final String name = ident.getName();
final Symbol symbol = ident.getSymbol();
- final boolean failDelete = strictMode || symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel());
+ final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel())));
if (failDelete && symbol.isThis()) {
return LiteralNode.newInstance(unaryNode, true).accept(this);
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Tue Jul 01 14:27:28 2014 -0700
@@ -145,6 +145,7 @@
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
import jdk.nashorn.internal.runtime.RewriteException;
import jdk.nashorn.internal.runtime.Scope;
+import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
@@ -210,6 +211,9 @@
* by reflection in class installation */
private final Compiler compiler;
+ /** Is the current code submitted by 'eval' call? */
+ private final boolean evalCode;
+
/** Call site flags given to the code generator to be used for all generated call sites */
private final int callSiteFlags;
@@ -264,6 +268,7 @@
CodeGenerator(final Compiler compiler, final int[] continuationEntryPoints) {
super(new CodeGeneratorLexicalContext());
this.compiler = compiler;
+ this.evalCode = compiler.getSource().isEvalCode();
this.continuationEntryPoints = continuationEntryPoints;
this.callSiteFlags = compiler.getScriptEnvironment()._callsite_flags;
this.log = initLogger(compiler.getContext());
@@ -290,6 +295,14 @@
}
/**
+ * Are we generating code for 'eval' code?
+ * @return true if currently compiled code is 'eval' code.
+ */
+ boolean isEvalCode() {
+ return evalCode;
+ }
+
+ /**
* Load an identity node
*
* @param identNode an identity node to load
@@ -1084,7 +1097,7 @@
closeBlockVariables(block);
lc.releaseSlots();
- assert !method.isReachable() || lc.getUsedSlotCount() == method.getFirstTemp();
+ assert !method.isReachable() || (lc.isFunctionBody() ? 0 : lc.getUsedSlotCount()) == method.getFirstTemp();
return block;
}
@@ -1277,13 +1290,26 @@
int argsCount;
@Override
void loadStack() {
- loadExpressionAsObject(ident); // Type.OBJECT as foo() makes no sense if foo == 3
- method.dup();
+ /**
+ * We want to load 'eval' to check if it is indeed global builtin eval.
+ * If this eval call is inside a 'with' statement, dyn:getMethod|getProp|getElem
+ * would be generated if ident is a "isFunction". But, that would result in a
+ * bound function from WithObject. We don't want that as bound function as that
+ * won't be detected as builtin eval. So, we make ident as "not a function" which
+ * results in "dyn:getProp|getElem|getMethod" being generated and so WithObject
+ * would return unbounded eval function.
+ *
+ * Example:
+ *
+ * var global = this;
+ * function func() {
+ * with({ eval: global.eval) { eval("var x = 10;") }
+ * }
+ */
+ loadExpressionAsObject(ident.setIsNotFunction()); // Type.OBJECT as foo() makes no sense if foo == 3
globalIsEval();
method.ifeq(is_not_eval);
- // We don't need ScriptFunction object for 'eval'
- method.pop();
// Load up self (scope).
method.loadCompilerConstant(SCOPE);
final CallNode.EvalArgs evalArgs = callNode.getEvalArgs();
@@ -1303,6 +1329,8 @@
method._goto(invoke_direct_eval);
method.label(is_not_eval);
+ // load this time but with dyn:getMethod|getProp|getElem
+ loadExpressionAsObject(ident); // Type.OBJECT as foo() makes no sense if foo == 3
// This is some scope 'eval' or global eval replaced by user
// but not the built-in ECMAScript 'eval' function call
method.loadNull();
@@ -1821,19 +1849,40 @@
method.storeCompilerConstant(ARGUMENTS);
}
- /**
- * Should this code generator skip generating code for inner functions? If lazy compilation is on, or we're
- * doing an on-demand ("just-in-time") compilation, then we aren't generating code for inner functions.
- */
- private boolean compileOutermostOnly() {
- return compiler.isOnDemandCompilation() || compiler.getScriptEnvironment()._lazy_compilation;
+ private boolean skipFunction(final FunctionNode functionNode) {
+ final ScriptEnvironment env = compiler.getScriptEnvironment();
+ final boolean lazy = env._lazy_compilation;
+ final boolean onDemand = compiler.isOnDemandCompilation();
+
+ // If this is on-demand or lazy compilation, don't compile a nested (not topmost) function.
+ if((onDemand || lazy) && lc.getOutermostFunction() != functionNode) {
+ return true;
+ }
+
+ // If lazy compiling with optimistic types, don't compile the program eagerly either. It will soon be
+ // invalidated anyway. In presence of a class cache, this further means that an obsoleted program version
+ // lingers around. Also, currently loading previously persisted optimistic types information only works if
+ // we're on-demand compiling a function, so with this strategy the :program method can also have the warmup
+ // benefit of using previously persisted types.
+ // NOTE that this means the first compiled class will effectively just have a :createProgramFunction method, and
+ // the RecompilableScriptFunctionData (RSFD) object in its constants array. It won't even have the :program
+ // method. This is by design. It does mean that we're wasting one compiler execution (and we could minimize this
+ // by just running it up to scope depth calculation, which creates the RSFDs and then this limited codegen).
+ // We could emit an initial separate compile unit with the initial version of :program in it to better utilize
+ // the compilation pipeline, but that would need more invasive changes, as currently the assumption that
+ // :program is emitted into the first compilation unit of the function lives in many places.
+ if(!onDemand && lazy && env._optimistic_types && functionNode.isProgram()) {
+ return true;
+ }
+
+ return false;
}
@Override
public boolean enterFunctionNode(final FunctionNode functionNode) {
final int fnId = functionNode.getId();
- if (compileOutermostOnly() && lc.getOutermostFunction() != functionNode) {
+ if (skipFunction(functionNode)) {
// In case we are not generating code for the function, we must create or retrieve the function object and
// load it on the stack here.
newFunctionObject(functionNode, false);
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Tue Jul 01 14:27:28 2014 -0700
@@ -173,7 +173,18 @@
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final CompileUnit outermostCompileUnit = compiler.addCompileUnit(0L);
- final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn, true);
+
+ FunctionNode newFunctionNode;
+
+ //ensure elementTypes, postsets and presets exist for splitter and arraynodes
+ newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor(new LexicalContext()) {
+ @Override
+ public LiteralNode> leaveLiteralNode(final LiteralNode> literalNode) {
+ return literalNode.initialize(lc);
+ }
+ });
+
+ newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true);
assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName());
@@ -374,7 +385,7 @@
assert newUnit != null;
newArrayUnits.add(new ArrayUnit(newUnit, au.getLo(), au.getHi()));
}
- aln.setUnits(newArrayUnits);
+ return aln.setUnits(lc, newArrayUnits);
}
return node;
}
@@ -421,7 +432,9 @@
compiler.getLogger().fine("Starting bytecode generation for ", quote(fn.getName()), " - restOf=", phases.isRestOfCompilation());
final CodeGenerator codegen = new CodeGenerator(compiler, phases.isRestOfCompilation() ? compiler.getContinuationEntryPoints() : null);
try {
- newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen);
+ // Explicitly set BYTECODE_GENERATED here; it can not be set in case of skipping codegen for :program
+ // in the lazy + optimistic world. See CodeGenerator.skipFunction().
+ newFunctionNode = ((FunctionNode)newFunctionNode.accept(codegen)).setState(null, BYTECODE_GENERATED);
codegen.generateScopeCalls();
} catch (final VerifyError e) {
if (senv._verify_code || senv._print_code) {
@@ -489,7 +502,7 @@
Class> rootClass = null;
long length = 0L;
- final CodeInstaller> codeInstaller = compiler.getCodeInstaller();
+ final CodeInstaller codeInstaller = compiler.getCodeInstaller();
final Map bytecode = compiler.getBytecode();
@@ -514,12 +527,10 @@
final Object[] constants = compiler.getConstantData().toArray();
codeInstaller.initialize(installedClasses.values(), compiler.getSource(), constants);
- // index recompilable script function datas in the constant pool
- final Map rfns = new IdentityHashMap<>();
+ // initialize transient fields on recompilable script function data
for (final Object constant: constants) {
if (constant instanceof RecompilableScriptFunctionData) {
- final RecompilableScriptFunctionData rfn = (RecompilableScriptFunctionData)constant;
- rfns.put(rfn, rfn);
+ ((RecompilableScriptFunctionData)constant).initTransients(compiler.getSource(), codeInstaller);
}
}
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Tue Jul 01 14:27:28 2014 -0700
@@ -46,10 +46,10 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.logging.Level;
-
import jdk.internal.dynalink.support.NameCodec;
import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
import jdk.nashorn.internal.codegen.types.Type;
@@ -123,6 +123,11 @@
private final Map invalidatedProgramPoints;
/**
+ * Descriptor of the location where we write the type information after compilation.
+ */
+ private final Object typeInformationFile;
+
+ /**
* Compile unit name of first compile unit - this prefix will be used for all
* classes that a compilation generates.
*/
@@ -317,7 +322,7 @@
final Source source,
final String sourceURL,
final boolean isStrict) {
- this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null);
+ this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null, null);
}
/**
@@ -333,6 +338,7 @@
* @param compiledFunction compiled function, if any
* @param types parameter and return value type information, if any is known
* @param invalidatedProgramPoints invalidated program points for recompilation
+ * @param typeInformationFile descriptor of the location where type information is persisted
* @param continuationEntryPoints continuation entry points for restof method
* @param runtimeScope runtime scope for recompilation type lookup in {@code TypeEvaluator}
*/
@@ -347,6 +353,7 @@
final RecompilableScriptFunctionData compiledFunction,
final TypeMap types,
final Map invalidatedProgramPoints,
+ final Object typeInformationFile,
final int[] continuationEntryPoints,
final ScriptObject runtimeScope) {
this.context = context;
@@ -363,6 +370,7 @@
this.compiledFunction = compiledFunction;
this.types = types;
this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap() : invalidatedProgramPoints;
+ this.typeInformationFile = typeInformationFile;
this.continuationEntryPoints = continuationEntryPoints == null ? null: continuationEntryPoints.clone();
this.typeEvaluator = new TypeEvaluator(this, runtimeScope);
this.firstCompileUnitName = firstCompileUnitName();
@@ -457,6 +465,16 @@
invalidatedProgramPoints.put(programPoint, type);
}
+
+ /**
+ * Returns a copy of this compiler's current mapping of invalidated optimistic program points to their types. The
+ * copy is not live with regard to changes in state in this compiler instance, and is mutable.
+ * @return a copy of this compiler's current mapping of invalidated optimistic program points to their types.
+ */
+ public Map getInvalidatedProgramPoints() {
+ return invalidatedProgramPoints == null ? null : new TreeMap<>(invalidatedProgramPoints);
+ }
+
TypeMap getTypeMap() {
return types;
}
@@ -513,6 +531,10 @@
time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L);
}
+ if(typeInformationFile != null && !phases.isRestOfCompilation()) {
+ OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints);
+ }
+
log.unindent();
if (info) {
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java Tue Jul 01 14:27:28 2014 -0700
@@ -62,6 +62,8 @@
/** call site flags to be used for invocations */
private final int callSiteFlags;
+ /** are we creating this field object from 'eval' code? */
+ private final boolean evalCode;
/**
* Constructor
@@ -88,7 +90,7 @@
FieldObjectCreator(final CodeGenerator codegen, final List> tuples, final boolean isScope, final boolean hasArguments) {
super(codegen, tuples, isScope, hasArguments);
this.callSiteFlags = codegen.getCallSiteFlags();
-
+ this.evalCode = codegen.isEvalCode();
countFields();
findClass();
}
@@ -153,7 +155,7 @@
@Override
protected PropertyMap makeMap() {
assert propertyMap == null : "property map already initialized";
- propertyMap = newMapCreator(fieldObjectClass).makeFieldMap(hasArguments(), fieldCount, paddedFieldCount);
+ propertyMap = newMapCreator(fieldObjectClass).makeFieldMap(hasArguments(), fieldCount, paddedFieldCount, evalCode);
return propertyMap;
}
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java Tue Jul 01 14:27:28 2014 -0700
@@ -141,7 +141,7 @@
paramTypeList.add(paramType.getTypeClass());
}
- this.methodType = MH.type(returnType.getTypeClass(), paramTypeList.toArray(new Class[paramTypes.length]));
+ this.methodType = MH.type(returnType.getTypeClass(), paramTypeList.toArray(new Class>[paramTypes.length]));
}
/**
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Tue Jul 01 14:27:28 2014 -0700
@@ -25,10 +25,12 @@
package jdk.nashorn.internal.codegen;
+import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
import static jdk.nashorn.internal.ir.Expression.isAlwaysFalse;
import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
@@ -63,7 +65,6 @@
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LexicalContextNode;
import jdk.nashorn.internal.ir.LiteralNode;
-import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LocalVariableConversion;
import jdk.nashorn.internal.ir.LoopNode;
import jdk.nashorn.internal.ir.Node;
@@ -72,6 +73,7 @@
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.SplitNode;
+import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.TernaryNode;
@@ -356,6 +358,8 @@
private boolean reachable = true;
// Return type of the function
private Type returnType = Type.UNKNOWN;
+ // Synthetic return node that we must insert at the end of the function if it's end is reachable.
+ private ReturnNode syntheticReturn;
// Topmost current split node (if any)
private SplitNode topSplit;
@@ -583,7 +587,11 @@
}
}
setCompilerConstantAsObject(functionNode, CompilerConstants.THIS);
- if(functionNode.needsParentScope()) {
+
+ // TODO: coarse-grained. If we wanted to solve it completely precisely,
+ // we'd also need to push/pop its type when handling WithNode (so that
+ // it can go back to undefined after a 'with' block.
+ if(functionNode.hasScopeBlock() || functionNode.needsParentScope()) {
setCompilerConstantAsObject(functionNode, CompilerConstants.SCOPE);
}
if(functionNode.needsCallee()) {
@@ -841,6 +849,10 @@
@Override
public boolean enterThrowNode(final ThrowNode throwNode) {
+ if(!reachable) {
+ return false;
+ }
+
throwNode.getExpression().accept(this);
jumpToCatchBlock(throwNode);
doesNotContinueSequentially();
@@ -1027,6 +1039,15 @@
@Override
public Node leaveBlock(final Block block) {
if(lc.isFunctionBody()) {
+ if(reachable) {
+ // reachable==true means we can reach the end of the function without an explicit return statement. We
+ // need to insert a synthetic one then. This logic used to be in Lower.leaveBlock(), but Lower's
+ // reachability analysis (through Terminal.isTerminal() flags) is not precise enough so
+ // Lower$BlockLexicalContext.afterSetStatements will sometimes think the control flow terminates even
+ // when it didn't. Example: function() { switch((z)) { default: {break; } throw x; } }.
+ createSyntheticReturn(block);
+ assert !reachable;
+ }
// We must calculate the return type here (and not in leaveFunctionNode) as it can affect the liveness of
// the :return symbol and thus affect conversion type liveness calculations for it.
calculateReturnType();
@@ -1085,6 +1106,23 @@
retSymbol.setNeedsSlot(true);
}
}
+
+ private void createSyntheticReturn(final Block body) {
+ final FunctionNode functionNode = lc.getCurrentFunction();
+ final long token = functionNode.getToken();
+ final int finish = functionNode.getFinish();
+ final List statements = body.getStatements();
+ final int lineNumber = statements.isEmpty() ? functionNode.getLineNumber() : statements.get(statements.size() - 1).getLineNumber();
+ final IdentNode returnExpr;
+ if(functionNode.isProgram()) {
+ returnExpr = new IdentNode(token, finish, RETURN.symbolName()).setSymbol(getCompilerConstantSymbol(functionNode, RETURN));
+ } else {
+ returnExpr = null;
+ }
+ syntheticReturn = new ReturnNode(lineNumber, token, finish, returnExpr);
+ syntheticReturn.accept(this);
+ }
+
/**
* Leave a breakable node. If there's a join point associated with its break label (meaning there was at least one
* break statement to the end of the node), insert the join point into the flow.
@@ -1158,7 +1196,9 @@
} else if(binaryNode.isOptimisticUndecidedType()) {
// At this point, we can assign a static type to the optimistic binary ADD operator as now we know
// the types of its operands.
- return binaryNode.setType(Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()));
+ final Type type = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType());
+ // Use Type.CHARSEQUENCE instead of Type.STRING to avoid conversion of ConsStrings to Strings.
+ return binaryNode.setType(type.equals(Type.STRING) ? Type.CHARSEQUENCE : type);
}
return binaryNode;
}
@@ -1174,6 +1214,16 @@
}
@Override
+ public Node leaveBlock(final Block block) {
+ if(inOuterFunction && syntheticReturn != null && lc.isFunctionBody()) {
+ final ArrayList stmts = new ArrayList<>(block.getStatements());
+ stmts.add((ReturnNode)syntheticReturn.accept(this));
+ return block.setStatements(lc, stmts);
+ }
+ return super.leaveBlock(block);
+ }
+
+ @Override
public Node leaveFunctionNode(final FunctionNode nestedFunctionNode) {
inOuterFunction = true;
final FunctionNode newNestedFunction = (FunctionNode)nestedFunctionNode.accept(
@@ -1207,10 +1257,10 @@
@Override
public Node leaveLiteralNode(final LiteralNode> literalNode) {
- if(literalNode instanceof ArrayLiteralNode) {
- ((ArrayLiteralNode)literalNode).analyze();
- }
- return literalNode;
+ //for e.g. ArrayLiteralNodes the initial types may have been narrowed due to the
+ //introduction of optimistic behavior - hence ensure that all literal nodes are
+ //reinitialized
+ return literalNode.initialize(lc);
}
@Override
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/Lower.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java Tue Jul 01 14:27:28 2014 -0700
@@ -75,7 +75,6 @@
import jdk.nashorn.internal.runtime.CodeInstaller;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.JSType;
-import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.Source;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import jdk.nashorn.internal.runtime.logging.Loggable;
@@ -160,30 +159,6 @@
}
@Override
- public Node leaveBlock(final Block block) {
- //now we have committed the entire statement list to the block, but we need to truncate
- //whatever is after the last terminal. block append won't append past it
-
-
- if (lc.isFunctionBody()) {
- final FunctionNode currentFunction = lc.getCurrentFunction();
- final boolean isProgram = currentFunction.isProgram();
- final Statement last = lc.getLastStatement();
- final ReturnNode returnNode = new ReturnNode(
- last == null ? currentFunction.getLineNumber() : last.getLineNumber(), //TODO?
- currentFunction.getToken(),
- currentFunction.getFinish(),
- isProgram ?
- compilerConstant(RETURN) :
- LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED));
-
- returnNode.accept(this);
- }
-
- return block;
- }
-
- @Override
public boolean enterBreakNode(final BreakNode breakNode) {
addStatement(breakNode);
return false;
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java Wed Jul 05 19:47:10 2017 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java Tue Jul 01 14:27:28 2014 -0700
@@ -63,13 +63,13 @@
/**
* Constructs a property map based on a set of fields.
*
- * @param hasArguments does the created object have an "arguments" property
+ * @param hasArguments does the created object have an "arguments" property
* @param fieldCount Number of fields in use.
- * @param fieldMaximum Number of fields available.
- *
+ * @param fieldMaximum Number of fields available.
+ * @param evalCode is this property map created for 'eval' code?
* @return New map populated with accessor properties.
*/
- PropertyMap makeFieldMap(final boolean hasArguments, final int fieldCount, final int fieldMaximum) {
+ PropertyMap makeFieldMap(final boolean hasArguments, final int fieldCount, final int fieldMaximum, final boolean evalCode) {
final List properties = new ArrayList<>();
assert tuples != null;
@@ -79,7 +79,7 @@
final Class> initialType = tuple.getValueType();
if (symbol != null && !isValidArrayIndex(getArrayIndex(key))) {
- final int flags = getPropertyFlags(symbol, hasArguments);
+ final int flags = getPropertyFlags(symbol, hasArguments, evalCode);
final Property property = new AccessorProperty(
key,
flags,
@@ -104,7 +104,7 @@
//TODO initial type is object here no matter what. Is that right?
if (symbol != null && !isValidArrayIndex(getArrayIndex(key))) {
- final int flags = getPropertyFlags(symbol, hasArguments);
+ final int flags = getPropertyFlags(symbol, hasArguments, false);
properties.add(
new SpillProperty(
key,
@@ -124,7 +124,7 @@
*
* @return flags to use for fields
*/
- static int getPropertyFlags(final Symbol symbol, final boolean hasArguments) {
+ static int getPropertyFlags(final Symbol symbol, final boolean hasArguments, final boolean evalCode) {
int flags = 0;
if (symbol.isParam()) {
@@ -135,7 +135,13 @@
flags |= Property.HAS_ARGUMENTS;
}
- if (symbol.isScope()) {
+ // See ECMA 5.1 10.5 Declaration Binding Instantiation.
+ // Step 2 If code is eval code, then let configurableBindings
+ // be true else let configurableBindings be false.
+ // We have to make vars, functions declared in 'eval' code
+ // configurable. But vars, functions from any other code is
+ // not configurable.
+ if (symbol.isScope() && !evalCode) {
flags |= Property.NOT_CONFIGURABLE;
}
diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java Tue Jul 01 14:27:28 2014 -0700
@@ -0,0 +1,345 @@
+/*
+ * 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.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.MessageDigest;
+import java.security.PrivilegedAction;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Map;
+import java.util.TreeMap;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
+import jdk.nashorn.internal.runtime.Source;
+import jdk.nashorn.internal.runtime.logging.DebugLogger;
+import jdk.nashorn.internal.runtime.options.Options;
+
+/**
+ * Static utility that encapsulates persistence of decompilation information for functions. Normally, the type info
+ * persistence feature is enabled and operates in an operating-system specific per-user cache directory. You can
+ * override the directory by specifying it in the {@code nashorn.typeInfo.cacheDir} directory. Also, you can disable the
+ * type info persistence altogether by specifying the {@code nashorn.typeInfo.disabled} system property.
+ */
+public final class OptimisticTypesPersistence {
+ private static final File cacheDir = createCacheDir();
+ // In-process locks to make sure we don't have a cross-thread race condition manipulating any file.
+ private static final Object[] locks = cacheDir == null ? null : createLockArray();
+
+ // Only report one read/write error every minute
+ private static final long ERROR_REPORT_THRESHOLD = 60000L;
+
+ private static volatile long lastReportedError;
+
+ /**
+ * Retrieves an opaque descriptor for the persistence location for a given function. It should be passed to
+ * {@link #load(Object)} and {@link #store(Object, Map)} methods.
+ * @param source the source where the function comes from
+ * @param functionId the unique ID number of the function within the source
+ * @param paramTypes the types of the function parameters (as persistence is per parameter type specialization).
+ * @return an opaque descriptor for the persistence location. Can be null if persistence is disabled.
+ */
+ public static Object getLocationDescriptor(final Source source, final int functionId, final Type[] paramTypes) {
+ if(cacheDir == null) {
+ return null;
+ }
+ final StringBuilder b = new StringBuilder(48);
+ // Base64-encode the digest of the source, and append the function id.
+ b.append(Base64.getUrlEncoder().encodeToString(source.getDigest())).append('-').append(functionId);
+ // Finally, if this is a parameter-type specialized version of the function, add the parameter types to the file
+ // name.
+ if(paramTypes != null && paramTypes.length > 0) {
+ b.append('-');
+ for(final Type t: paramTypes) {
+ b.append(t.getBytecodeStackType());
+ }
+ }
+ return new LocationDescriptor(new File(cacheDir, b.toString()));
+ }
+
+ private static final class LocationDescriptor {
+ private final File file;
+
+ LocationDescriptor(final File file) {
+ this.file = file;
+ }
+ }
+
+
+ /**
+ * Stores the map of optimistic types for a given function.
+ * @param locationDescriptor the opaque persistence location descriptor, retrieved by calling
+ * {@link #getLocationDescriptor(Source, int, Type[])}.
+ * @param optimisticTypes the map of optimistic types.
+ */
+ @SuppressWarnings("resource")
+ public static void store(final Object locationDescriptor, final Map optimisticTypes) {
+ if(locationDescriptor == null) {
+ return;
+ }
+ final File file = ((LocationDescriptor)locationDescriptor).file;
+ AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public Void run() {
+ synchronized(getFileLock(file)) {
+ try (final FileOutputStream out = new FileOutputStream(file);) {
+ out.getChannel().lock(); // lock exclusive
+ final DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(out));
+ dout.writeInt(optimisticTypes.size());
+ for(Map.Entry e: optimisticTypes.entrySet()) {
+ dout.writeInt(e.getKey());
+ final byte typeChar;
+ final Type type = e.getValue();
+ if(type == Type.OBJECT) {
+ typeChar = 'L';
+ } else if(type == Type.NUMBER) {
+ typeChar = 'D';
+ } else if(type == Type.LONG) {
+ typeChar = 'J';
+ } else {
+ throw new AssertionError();
+ }
+ dout.write(typeChar);
+ }
+ dout.flush();
+ } catch(final Exception e) {
+ reportError("write", file, e);
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Loads the map of optimistic types for a given function.
+ * @param locationDescriptor the opaque persistence location descriptor, retrieved by calling
+ * {@link #getLocationDescriptor(Source, int, Type[])}.
+ * @return the map of optimistic types, or null if persisted type information could not be retrieved.
+ */
+ @SuppressWarnings("resource")
+ public static Map load(final Object locationDescriptor) {
+ if (locationDescriptor == null) {
+ return null;
+ }
+ final File file = ((LocationDescriptor)locationDescriptor).file;
+
+ return AccessController.doPrivileged(new PrivilegedAction