8053905: Eager code generation fails for earley boyer with split threshold set to 1000

Reviewed-by: attila, lagergren
This commit is contained in:
Hannes Wallnöfer 2015-04-27 12:50:21 +02:00
parent 9becc306ef
commit c8634327e8
12 changed files with 159 additions and 183 deletions

View File

@ -4526,7 +4526,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
}
if (addInitializer && !compiler.isOnDemandCompilation()) {
compiler.addFunctionInitializer(data, functionNode);
functionNode.getCompileUnit().addFunctionInitializer(data, functionNode);
}
// We don't emit a ScriptFunction on stack for the outermost compiled function (as there's no code being

View File

@ -57,7 +57,6 @@ import jdk.nashorn.internal.ir.debug.ASTWriter;
import jdk.nashorn.internal.ir.debug.PrintVisitor;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.CodeInstaller;
import jdk.nashorn.internal.runtime.FunctionInitializer;
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
@ -581,18 +580,7 @@ enum CompilationPhase {
continue;
}
unit.setCode(installedClasses.get(unit.getUnitClassName()));
}
if (!compiler.isOnDemandCompilation()) {
// Initialize functions
final Map<Integer, FunctionInitializer> initializers = compiler.getFunctionInitializers();
if (initializers != null) {
for (final Entry<Integer, FunctionInitializer> entry : initializers.entrySet()) {
final FunctionInitializer initializer = entry.getValue();
initializer.setCode(installedClasses.get(initializer.getClassName()));
compiler.getScriptFunctionData(entry.getKey()).initializeCode(initializer);
}
}
unit.initializeFunctionsCode();
}
if (log.isEnabled()) {

View File

@ -26,10 +26,16 @@
package jdk.nashorn.internal.codegen;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import jdk.nashorn.internal.ir.CompileUnitHolder;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
/**
* Used to track split class compilation. Note that instances of the class are serializable, but all fields are
@ -50,6 +56,8 @@ public final class CompileUnit implements Comparable<CompileUnit>, Serializable
private transient Class<?> clazz;
private transient Map<FunctionNode, RecompilableScriptFunctionData> functions = new IdentityHashMap<>();
private transient boolean isUsed;
private static int emittedUnitCount;
@ -121,6 +129,32 @@ public final class CompileUnit implements Comparable<CompileUnit>, Serializable
this.classEmitter = null;
}
void addFunctionInitializer(final RecompilableScriptFunctionData data, final FunctionNode functionNode) {
functions.put(functionNode, data);
}
/**
* Returns true if this compile unit is responsible for initializing the specified function data with specified
* function node.
* @param data the function data to check
* @param functionNode the function node to check
* @return true if this unit is responsible for initializing the function data with the function node, otherwise
* false
*/
public boolean isInitializing(final RecompilableScriptFunctionData data, final FunctionNode functionNode) {
return functions.get(functionNode) == data;
}
void initializeFunctionsCode() {
for(final Map.Entry<FunctionNode, RecompilableScriptFunctionData> entry : functions.entrySet()) {
entry.getValue().initializeCode(entry.getKey());
}
}
Collection<FunctionNode> getFunctionNodes() {
return Collections.unmodifiableCollection(functions.keySet());
}
/**
* Add weight to this compile unit
* @param w weight to add

View File

@ -565,7 +565,7 @@ public final class Compiler implements Loggable {
* @return a copy of this compiler's current mapping of invalidated optimistic program points to their types.
*/
public Map<Integer, Type> getInvalidatedProgramPoints() {
return invalidatedProgramPoints == null ? null : new TreeMap<>(invalidatedProgramPoints);
return invalidatedProgramPoints.isEmpty() ? null : new TreeMap<>(invalidatedProgramPoints);
}
TypeMap getTypeMap() {
@ -704,21 +704,6 @@ public final class Compiler implements Loggable {
return sb.toString();
}
Map<Integer, FunctionInitializer> functionInitializers;
void addFunctionInitializer(final RecompilableScriptFunctionData functionData, final FunctionNode functionNode) {
if (functionInitializers == null) {
functionInitializers = new HashMap<>();
}
if (!functionInitializers.containsKey(functionData)) {
functionInitializers.put(functionData.getFunctionNodeId(), new FunctionInitializer(functionNode));
}
}
Map<Integer, FunctionInitializer> getFunctionInitializers() {
return functionInitializers;
}
/**
* Persist current compilation with the given {@code cacheKey}.
* @param cacheKey cache key
@ -726,15 +711,17 @@ public final class Compiler implements Loggable {
*/
public void persistClassInfo(final String cacheKey, final FunctionNode functionNode) {
if (cacheKey != null && env._persistent_cache) {
Map<Integer, FunctionInitializer> initializers;
// If this is an on-demand compilation create a function initializer for the function being compiled.
// Otherwise use function initializer map generated by codegen.
if (functionInitializers == null) {
initializers = new HashMap<>();
final FunctionInitializer initializer = new FunctionInitializer(functionNode, getInvalidatedProgramPoints());
initializers.put(functionNode.getId(), initializer);
Map<Integer, FunctionInitializer> initializers = new HashMap<>();
if (isOnDemandCompilation()) {
initializers.put(functionNode.getId(), new FunctionInitializer(functionNode, getInvalidatedProgramPoints()));
} else {
initializers = functionInitializers;
for (final CompileUnit compileUnit : getCompileUnits()) {
for (final FunctionNode fn : compileUnit.getFunctionNodes()) {
initializers.put(fn.getId(), new FunctionInitializer(fn));
}
}
}
final String mainClassName = getFirstCompileUnit().getUnitClassName();
installer.storeScript(cacheKey, source, mainClassName, bytecode, initializers, constantData.toArray(), compilationId);

View File

@ -227,7 +227,8 @@ final class TypeEvaluator {
// gradually introduce them as needed. An easy one would be to do the same for .call(this) idiom.
final CallNode callExpr = (CallNode)expr;
final Expression fnExpr = callExpr.getFunction();
if (fnExpr instanceof FunctionNode) {
// Skip evaluation if running with eager compilation as we may violate constraints in RecompilableScriptFunctionData
if (fnExpr instanceof FunctionNode && compiler.getContext().getEnv()._lazy_compilation) {
final FunctionNode fn = (FunctionNode)fnExpr;
if (callExpr.getArgs().isEmpty()) {
final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(fn.getId());

View File

@ -1281,7 +1281,7 @@ public final class Context {
compiler.persistClassInfo(cacheKey, compiledFunction);
} else {
Compiler.updateCompilationId(storedScript.getCompilationId());
script = install(storedScript, source, installer);
script = storedScript.installScript(source, installer);
}
cacheClass(source, script);
@ -1302,51 +1302,6 @@ public final class Context {
return uniqueScriptId.getAndIncrement();
}
/**
* Install a previously compiled class from the code cache.
*
* @param storedScript cached script containing class bytes and constants
* @return main script class
*/
private static Class<?> install(final StoredScript storedScript, final Source source, final CodeInstaller<ScriptEnvironment> installer) {
final Map<String, Class<?>> installedClasses = new HashMap<>();
final Map<String, byte[]> classBytes = storedScript.getClassBytes();
final Object[] constants = storedScript.getConstants();
final String mainClassName = storedScript.getMainClassName();
final byte[] mainClassBytes = classBytes.get(mainClassName);
final Class<?> mainClass = installer.install(mainClassName, mainClassBytes);
final Map<Integer, FunctionInitializer> initializers = storedScript.getInitializers();
installedClasses.put(mainClassName, mainClass);
for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
final String className = entry.getKey();
if (className.equals(mainClassName)) {
continue;
}
final byte[] code = entry.getValue();
installedClasses.put(className, installer.install(className, code));
}
installer.initialize(installedClasses.values(), source, constants);
for (final Object constant : constants) {
if (constant instanceof RecompilableScriptFunctionData) {
final RecompilableScriptFunctionData data = (RecompilableScriptFunctionData) constant;
data.initTransients(source, installer);
final FunctionInitializer initializer = initializers.get(data.getFunctionNodeId());
if (initializer != null) {
initializer.setCode(installedClasses.get(initializer.getClassName()));
data.initializeCode(initializer);
}
}
}
return mainClass;
}
/**
* Cache for compiled script classes.
*/

View File

@ -59,17 +59,6 @@ public final class FunctionInitializer implements Serializable {
this(functionNode, null);
}
/**
* Copy constructor.
*
* @param init original initializer
*/
FunctionInitializer(final FunctionInitializer init) {
this.className = init.getClassName();
this.methodType = init.getMethodType();
this.flags = init.getFlags();
}
/**
* Constructor.
*
@ -130,7 +119,7 @@ public final class FunctionInitializer implements Serializable {
* Set the class implementing the function
* @param code the class
*/
public void setCode(final Class<?> code) {
void setCode(final Class<?> code) {
// Make sure code has not been set and has expected class name
if (this.code != null) {
throw new IllegalStateException("code already set");

View File

@ -32,7 +32,6 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@ -503,7 +502,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
if (script != null) {
Compiler.updateCompilationId(script.getCompilationId());
return installStoredScript(script, newInstaller);
return script.installFunction(this, newInstaller);
}
}
@ -518,56 +517,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());
}
private static Map<String, Class<?>> installStoredScriptClasses(final StoredScript script, final CodeInstaller<ScriptEnvironment> installer) {
final Map<String, Class<?>> installedClasses = new HashMap<>();
final Map<String, byte[]> classBytes = script.getClassBytes();
final String mainClassName = script.getMainClassName();
final byte[] mainClassBytes = classBytes.get(mainClassName);
final Class<?> mainClass = installer.install(mainClassName, mainClassBytes);
installedClasses.put(mainClassName, mainClass);
for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
final String className = entry.getKey();
final byte[] bytecode = entry.getValue();
if (className.equals(mainClassName)) {
continue;
}
installedClasses.put(className, installer.install(className, bytecode));
}
return installedClasses;
}
/**
* Install this script using the given {@code installer}.
*
* @param script the compiled script
* @return the function initializer
*/
private FunctionInitializer installStoredScript(final StoredScript script, final CodeInstaller<ScriptEnvironment> newInstaller) {
final Map<String, Class<?>> installedClasses = installStoredScriptClasses(script, newInstaller);
final Map<Integer, FunctionInitializer> initializers = script.getInitializers();
assert initializers != null;
assert initializers.size() == 1;
final FunctionInitializer initializer = initializers.values().iterator().next();
final Object[] constants = script.getConstants();
for (int i = 0; i < constants.length; i++) {
if (constants[i] instanceof RecompilableScriptFunctionData) {
// replace deserialized function data with the ones we already have
constants[i] = getScriptFunctionData(((RecompilableScriptFunctionData) constants[i]).getFunctionNodeId());
}
}
newInstaller.initialize(installedClasses.values(), source, constants);
initializer.setCode(installedClasses.get(initializer.getClassName()));
return initializer;
}
boolean usePersistentCodeCache() {
final ScriptEnvironment env = installer.getOwner();
return env._persistent_cache && env._optimistic_types;
@ -645,13 +594,21 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
* by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it
* externally will result in an exception.
*
* @param initializer FunctionInitializer for this data
* @param functionNode FunctionNode for this data
*/
public void initializeCode(final FunctionInitializer initializer) {
public void initializeCode(final FunctionNode functionNode) {
// Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit.
if(!code.isEmpty()) {
if (!code.isEmpty() || functionNode.getId() != functionNodeId || !functionNode.getCompileUnit().isInitializing(this, functionNode)) {
throw new IllegalStateException(name);
}
addCode(lookup(functionNode), null, null, functionNode.getFlags());
}
/**
* Initializes this function with the given function code initializer.
* @param initializer function code initializer
*/
void initializeCode(final FunctionInitializer initializer) {
addCode(lookup(initializer, true), null, null, initializer.getFlags());
}

View File

@ -27,7 +27,7 @@ package jdk.nashorn.internal.runtime;
import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.Map;
/**
@ -77,44 +77,70 @@ public final class StoredScript implements Serializable {
return compilationId;
}
/**
* Returns the main class name.
* @return the main class name
*/
public String getMainClassName() {
return mainClassName;
}
private Map<String, Class<?>> installClasses(final Source source, final CodeInstaller<ScriptEnvironment> installer) {
final Map<String, Class<?>> installedClasses = new HashMap<>();
final byte[] mainClassBytes = classBytes.get(mainClassName);
final Class<?> mainClass = installer.install(mainClassName, mainClassBytes);
installedClasses.put(mainClassName, mainClass);
/**
* Returns a map of class names to class bytes.
* @return map of class bytes
*/
public Map<String, byte[]> getClassBytes() {
final Map<String, byte[]> clonedMap = new LinkedHashMap<>();
for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
clonedMap.put(entry.getKey(), entry.getValue().clone());
final String className = entry.getKey();
if (!className.equals(mainClassName)) {
installedClasses.put(className, installer.install(className, entry.getValue()));
}
}
return clonedMap;
installer.initialize(installedClasses.values(), source, constants);
return installedClasses;
}
FunctionInitializer installFunction(final RecompilableScriptFunctionData data, final CodeInstaller<ScriptEnvironment> installer) {
final Map<String, Class<?>> installedClasses = installClasses(data.getSource(), installer);
assert initializers != null;
assert initializers.size() == 1;
final FunctionInitializer initializer = initializers.values().iterator().next();
for (int i = 0; i < constants.length; i++) {
if (constants[i] instanceof RecompilableScriptFunctionData) {
// replace deserialized function data with the ones we already have
final RecompilableScriptFunctionData newData = data.getScriptFunctionData(((RecompilableScriptFunctionData) constants[i]).getFunctionNodeId());
assert newData != null;
newData.initTransients(data.getSource(), installer);
constants[i] = newData;
}
}
initializer.setCode(installedClasses.get(initializer.getClassName()));
return initializer;
}
/**
* Returns the constants array.
* @return constants array
* Install as script.
*
* @param source the source
* @param installer the installer
* @return main script class
*/
public Object[] getConstants() {
return constants.clone();
}
Class<?> installScript(final Source source, final CodeInstaller<ScriptEnvironment> installer) {
/**
* Returns the function initializers map.
* @return The initializers map.
*/
public Map<Integer, FunctionInitializer> getInitializers() {
final Map<Integer, FunctionInitializer> clonedMap = new LinkedHashMap<>();
for (final Map.Entry<Integer, FunctionInitializer> entry : initializers.entrySet()) {
clonedMap.put(entry.getKey(), new FunctionInitializer(entry.getValue()));
final Map<String, Class<?>> installedClasses = installClasses(source, installer);
for (final Object constant : constants) {
if (constant instanceof RecompilableScriptFunctionData) {
final RecompilableScriptFunctionData data = (RecompilableScriptFunctionData) constant;
data.initTransients(source, installer);
final FunctionInitializer initializer = initializers.get(data.getFunctionNodeId());
if (initializer != null) {
initializer.setCode(installedClasses.get(initializer.getClassName()));
data.initializeCode(initializer);
}
}
}
return clonedMap;
return installedClasses.get(mainClassName);
}
@Override

View File

@ -0,0 +1,38 @@
/*
* 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.
*
* 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.
*/
/**
* JDK-8053905: Eager code generation fails for earley boyer with split threshold set to 1000
*
* @test
* @runif external.octane
* @fork
* @option -Dnashorn.compiler.splitter.threshold=1000
* @option -scripting
* @option --lazy-compilation=false
*/
var fn = __DIR__ + 'compile-octane.js';
arguments.push("earley-boyer"); // run only earley-boyer
var url = new java.io.File(fn).toURL();
load(url);

View File

@ -0,0 +1,2 @@
Compiling 'earley-boyer'...
Done.

View File

@ -29,11 +29,10 @@
* forever, so make this test future safe, we specify them explicitly
*
* @test
* @fork
* @option -Dnashorn.compiler.splitter.threshold=1000
* @fork
* @runif external.octane
* @fork
* @option -scripting
* @option -Dnashorn.compiler.splitter.threshold=1000
* @option -Dnashorn.typeInfo.disabled=true
* @option --class-cache-size=0
* @option --persistent-code-cache=false