mirror of
https://github.com/openjdk/jdk.git
synced 2026-05-04 18:55:22 +00:00
8043956: Make code caching work with optimistic typing and lazy compilation
Reviewed-by: attila, lagergren
This commit is contained in:
parent
35c5f0ad5d
commit
a8ece5cffc
@ -217,7 +217,7 @@ test262-test-sys-prop.test.js.framework=\
|
||||
${test262.dir}/test/harness/sta.js
|
||||
|
||||
# testmarkdown test root
|
||||
testmarkdown-test-sys-prop.test.js.roots=${testmarkdown.dir}
|
||||
testmarkdown-test-sys-prop.test.js.roots=${testmarkdown.dir}
|
||||
|
||||
# execute testmarkdown tests in shared nashorn context or not?
|
||||
testmarkdown-test-sys-prop.test.js.shared.context=false
|
||||
@ -227,7 +227,7 @@ testmarkdown-test-sys-prop.test.js.framework=\
|
||||
${test.script.dir}${file.separator}markdown.js
|
||||
|
||||
# testjfx test root
|
||||
testjfx-test-sys-prop.test.js.roots=${testjfx.dir}
|
||||
testjfx-test-sys-prop.test.js.roots=${testjfx.dir}
|
||||
|
||||
# execute testjfx tests in shared nashorn context or not?
|
||||
testjfx-test-sys-prop.test.js.shared.context=false
|
||||
|
||||
@ -250,8 +250,6 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
|
||||
private final Deque<Label> scopeEntryLabels = new ArrayDeque<>();
|
||||
|
||||
private final Set<Integer> initializedFunctionIds = new HashSet<>();
|
||||
|
||||
private static final Label METHOD_BOUNDARY = new Label("");
|
||||
private final Deque<Label> catchLabels = new ArrayDeque<>();
|
||||
// Number of live locals on entry to (and thus also break from) labeled blocks.
|
||||
@ -1872,6 +1870,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
// 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
|
||||
@ -1879,11 +1878,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
// 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;
|
||||
return !onDemand && lazy && env._optimistic_types && functionNode.isProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -4383,9 +4378,8 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
createFunction.end();
|
||||
}
|
||||
|
||||
if (addInitializer && !initializedFunctionIds.contains(fnId) && !compiler.isOnDemandCompilation()) {
|
||||
functionNode.getCompileUnit().addFunctionInitializer(data, functionNode);
|
||||
initializedFunctionIds.add(fnId);
|
||||
if (addInitializer && !compiler.isOnDemandCompilation()) {
|
||||
compiler.addFunctionInitializer(data, functionNode);
|
||||
}
|
||||
|
||||
// We don't emit a ScriptFunction on stack for the outermost compiled function (as there's no code being
|
||||
|
||||
@ -61,6 +61,7 @@ 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;
|
||||
@ -324,15 +325,15 @@ enum CompilationPhase {
|
||||
final DebugLogger log = compiler.getLogger();
|
||||
|
||||
log.fine("Clearing bytecode cache");
|
||||
compiler.clearBytecode();
|
||||
|
||||
for (final CompileUnit oldUnit : compiler.getCompileUnits()) {
|
||||
CompileUnit newUnit = map.get(oldUnit);
|
||||
assert map.get(oldUnit) == null;
|
||||
final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName());
|
||||
if (phases.isRestOfCompilation()) {
|
||||
sb.append("$restOf");
|
||||
}
|
||||
newUnit = compiler.createCompileUnit(sb.toString(), oldUnit.getWeight());
|
||||
CompileUnit newUnit = compiler.createCompileUnit(sb.toString(), oldUnit.getWeight());
|
||||
log.fine("Creating new compile unit ", oldUnit, " => ", newUnit);
|
||||
map.put(oldUnit, newUnit);
|
||||
assert newUnit != null;
|
||||
@ -502,8 +503,7 @@ enum CompilationPhase {
|
||||
long length = 0L;
|
||||
|
||||
final CodeInstaller<ScriptEnvironment> codeInstaller = compiler.getCodeInstaller();
|
||||
|
||||
final Map<String, byte[]> bytecode = compiler.getBytecode();
|
||||
final Map<String, byte[]> bytecode = compiler.getBytecode();
|
||||
|
||||
for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
|
||||
final String className = entry.getKey();
|
||||
@ -536,17 +536,18 @@ enum CompilationPhase {
|
||||
// initialize function in the compile units
|
||||
for (final CompileUnit unit : compiler.getCompileUnits()) {
|
||||
unit.setCode(installedClasses.get(unit.getUnitClassName()));
|
||||
unit.initializeFunctionsCode();
|
||||
}
|
||||
|
||||
if (!compiler.isOnDemandCompilation()) {
|
||||
codeInstaller.storeCompiledScript(compiler.getSource(), compiler.getFirstCompileUnit().getUnitClassName(), bytecode, constants);
|
||||
}
|
||||
|
||||
// remove installed bytecode from table in case compiler is reused
|
||||
for (final String className : installedClasses.keySet()) {
|
||||
log.fine("Removing installed class ", quote(className), " from bytecode table...");
|
||||
compiler.removeClass(className);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isEnabled()) {
|
||||
|
||||
@ -46,36 +46,6 @@ public final class CompileUnit implements Comparable<CompileUnit> {
|
||||
|
||||
private Class<?> clazz;
|
||||
|
||||
private Set<FunctionInitializer> functionInitializers = new LinkedHashSet<>();
|
||||
|
||||
private static class FunctionInitializer {
|
||||
final RecompilableScriptFunctionData data;
|
||||
final FunctionNode functionNode;
|
||||
|
||||
FunctionInitializer(final RecompilableScriptFunctionData data, final FunctionNode functionNode) {
|
||||
this.data = data;
|
||||
this.functionNode = functionNode;
|
||||
}
|
||||
|
||||
void initializeCode() {
|
||||
data.initializeCode(functionNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return data.hashCode() + 31 * functionNode.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == null || obj.getClass() != FunctionInitializer.class) {
|
||||
return false;
|
||||
}
|
||||
final FunctionInitializer other = (FunctionInitializer)obj;
|
||||
return data == other.data && functionNode == other.functionNode;
|
||||
}
|
||||
}
|
||||
|
||||
CompileUnit(final String className, final ClassEmitter classEmitter, final long initialWeight) {
|
||||
this.className = className;
|
||||
this.weight = initialWeight;
|
||||
@ -108,29 +78,6 @@ public final class CompileUnit implements Comparable<CompileUnit> {
|
||||
this.classEmitter = null;
|
||||
}
|
||||
|
||||
void addFunctionInitializer(final RecompilableScriptFunctionData data, final FunctionNode functionNode) {
|
||||
functionInitializers.add(new FunctionInitializer(data, functionNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 functionInitializers.contains(new FunctionInitializer(data, functionNode));
|
||||
}
|
||||
|
||||
void initializeFunctionsCode() {
|
||||
for(final FunctionInitializer init : functionInitializers) {
|
||||
init.initializeCode();
|
||||
}
|
||||
functionInitializers = Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add weight to this compile unit
|
||||
* @param w weight to add
|
||||
|
||||
@ -50,7 +50,6 @@ 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;
|
||||
@ -60,6 +59,7 @@ import jdk.nashorn.internal.ir.debug.ClassHistogramElement;
|
||||
import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator;
|
||||
import jdk.nashorn.internal.runtime.CodeInstaller;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.FunctionInitializer;
|
||||
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
|
||||
import jdk.nashorn.internal.runtime.ScriptEnvironment;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
@ -89,8 +89,6 @@ public final class Compiler implements Loggable {
|
||||
|
||||
private final String sourceName;
|
||||
|
||||
private final String sourceURL;
|
||||
|
||||
private final boolean optimistic;
|
||||
|
||||
private final Map<String, byte[]> bytecode;
|
||||
@ -309,21 +307,19 @@ public final class Compiler implements Loggable {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context context
|
||||
* @param env script environment
|
||||
* @param installer code installer
|
||||
* @param source source to compile
|
||||
* @param sourceURL source URL, or null if not present
|
||||
* @param isStrict is this a strict compilation
|
||||
* @param context context
|
||||
* @param env script environment
|
||||
* @param installer code installer
|
||||
* @param source source to compile
|
||||
* @param isStrict is this a strict compilation
|
||||
*/
|
||||
public Compiler(
|
||||
final Context context,
|
||||
final ScriptEnvironment env,
|
||||
final CodeInstaller<ScriptEnvironment> installer,
|
||||
final Source source,
|
||||
final String sourceURL,
|
||||
final boolean isStrict) {
|
||||
this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null, null);
|
||||
this(context, env, installer, source, isStrict, false, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -333,7 +329,6 @@ public final class Compiler implements Loggable {
|
||||
* @param env script environment
|
||||
* @param installer code installer
|
||||
* @param source source to compile
|
||||
* @param sourceURL source URL, or null if not present
|
||||
* @param isStrict is this a strict compilation
|
||||
* @param isOnDemand is this an on demand compilation
|
||||
* @param compiledFunction compiled function, if any
|
||||
@ -348,7 +343,6 @@ public final class Compiler implements Loggable {
|
||||
final ScriptEnvironment env,
|
||||
final CodeInstaller<ScriptEnvironment> installer,
|
||||
final Source source,
|
||||
final String sourceURL,
|
||||
final boolean isStrict,
|
||||
final boolean isOnDemand,
|
||||
final RecompilableScriptFunctionData compiledFunction,
|
||||
@ -365,8 +359,7 @@ public final class Compiler implements Loggable {
|
||||
this.bytecode = new LinkedHashMap<>();
|
||||
this.log = initLogger(context);
|
||||
this.source = source;
|
||||
this.sourceURL = sourceURL;
|
||||
this.sourceName = FunctionNode.getSourceName(source, sourceURL);
|
||||
this.sourceName = FunctionNode.getSourceName(source);
|
||||
this.onDemand = isOnDemand;
|
||||
this.compiledFunction = compiledFunction;
|
||||
this.types = types;
|
||||
@ -411,6 +404,15 @@ public final class Compiler implements Loggable {
|
||||
sb.append(compilationId).append('$');
|
||||
}
|
||||
|
||||
if (types != null && compiledFunction.getFunctionNodeId() > 0) {
|
||||
sb.append(compiledFunction.getFunctionNodeId());
|
||||
final Type[] paramTypes = types.getParameterTypes(compiledFunction.getFunctionNodeId());
|
||||
for (final Type t : paramTypes) {
|
||||
sb.append(Type.getShortSignatureDescriptor(t));
|
||||
}
|
||||
sb.append('$');
|
||||
}
|
||||
|
||||
sb.append(Compiler.safeSourceName(env, installer, source));
|
||||
|
||||
return sb.toString();
|
||||
@ -559,8 +561,11 @@ public final class Compiler implements Loggable {
|
||||
return Collections.unmodifiableMap(bytecode);
|
||||
}
|
||||
|
||||
byte[] getBytecode(final String className) {
|
||||
return bytecode.get(className);
|
||||
/**
|
||||
* Reset bytecode cache for compiler reuse.
|
||||
*/
|
||||
void clearBytecode() {
|
||||
bytecode.clear();
|
||||
}
|
||||
|
||||
CompileUnit getFirstCompileUnit() {
|
||||
@ -584,15 +589,6 @@ public final class Compiler implements Loggable {
|
||||
bytecode.put(name, code);
|
||||
}
|
||||
|
||||
void removeClass(final String name) {
|
||||
assert bytecode.get(name) != null;
|
||||
bytecode.remove(name);
|
||||
}
|
||||
|
||||
String getSourceURL() {
|
||||
return sourceURL;
|
||||
}
|
||||
|
||||
String nextCompileUnitName() {
|
||||
final StringBuilder sb = new StringBuilder(firstCompileUnitName);
|
||||
final int cuid = nextCompileUnitId.getAndIncrement();
|
||||
@ -603,8 +599,51 @@ public final class Compiler implements Loggable {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
void clearCompileUnits() {
|
||||
compileUnits.clear();
|
||||
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
|
||||
* @param functionNode function node
|
||||
*/
|
||||
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);
|
||||
} else {
|
||||
initializers = functionInitializers;
|
||||
}
|
||||
final String mainClassName = getFirstCompileUnit().getUnitClassName();
|
||||
installer.storeScript(cacheKey, source, mainClassName, bytecode, initializers, constantData.toArray(), compilationId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the next compilation id is greater than {@code value}.
|
||||
* @param value compilation id value
|
||||
*/
|
||||
public static void updateCompilationId(final int value) {
|
||||
if (value >= COMPILATION_ID.get()) {
|
||||
COMPILATION_ID.set(value + 1);
|
||||
}
|
||||
}
|
||||
|
||||
CompileUnit addCompileUnit(final long initialWeight) {
|
||||
|
||||
@ -30,7 +30,6 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jdk.nashorn.internal.runtime.Property;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
|
||||
/**
|
||||
@ -121,12 +120,7 @@ final class ConstantData {
|
||||
private final int hashCode;
|
||||
|
||||
public PropertyMapWrapper(final PropertyMap map) {
|
||||
int hash = 0;
|
||||
for (final Property property : map.getProperties()) {
|
||||
hash = hash << 7 ^ hash >> 7;
|
||||
hash ^= property.hashCode();
|
||||
}
|
||||
this.hashCode = hash;
|
||||
this.hashCode = Arrays.hashCode(map.getProperties());
|
||||
this.propertyMap = map;
|
||||
}
|
||||
|
||||
@ -137,14 +131,8 @@ final class ConstantData {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (!(other instanceof PropertyMapWrapper)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Property[] ownProperties = propertyMap.getProperties();
|
||||
final Property[] otherProperties = ((PropertyMapWrapper) other).propertyMap.getProperties();
|
||||
|
||||
return Arrays.equals(ownProperties, otherProperties);
|
||||
return other instanceof PropertyMapWrapper &&
|
||||
Arrays.equals(propertyMap.getProperties(), ((PropertyMapWrapper) other).propertyMap.getProperties());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -217,7 +217,6 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> implements Logga
|
||||
allocatorClassName,
|
||||
allocatorMap,
|
||||
nestedFunctions,
|
||||
compiler.getSourceURL(),
|
||||
externalSymbolDepths.get(fnId),
|
||||
internalSymbols.get(fnId)
|
||||
);
|
||||
|
||||
@ -80,12 +80,11 @@ public final class OptimisticTypesPersistence {
|
||||
final StringBuilder b = new StringBuilder(48);
|
||||
// Base64-encode the digest of the source, and append the function id.
|
||||
b.append(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.
|
||||
// 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());
|
||||
b.append(Type.getShortSignatureDescriptor(t));
|
||||
}
|
||||
}
|
||||
return new LocationDescriptor(new File(cacheDir, b.toString()));
|
||||
@ -117,25 +116,10 @@ public final class OptimisticTypesPersistence {
|
||||
@Override
|
||||
public Void run() {
|
||||
synchronized(getFileLock(file)) {
|
||||
try (final FileOutputStream out = new FileOutputStream(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(final Map.Entry<Integer, Type> 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);
|
||||
}
|
||||
Type.writeTypeMap(optimisticTypes, dout);
|
||||
dout.flush();
|
||||
} catch(final Exception e) {
|
||||
reportError("write", file, e);
|
||||
@ -166,24 +150,10 @@ public final class OptimisticTypesPersistence {
|
||||
return null;
|
||||
}
|
||||
synchronized(getFileLock(file)) {
|
||||
try (final FileInputStream in = new FileInputStream(file);) {
|
||||
try (final FileInputStream in = new FileInputStream(file)) {
|
||||
in.getChannel().lock(0, Long.MAX_VALUE, true); // lock shared
|
||||
final DataInputStream din = new DataInputStream(new BufferedInputStream(in));
|
||||
final Map<Integer, Type> map = new TreeMap<>();
|
||||
final int size = din.readInt();
|
||||
for(int i = 0; i < size; ++i) {
|
||||
final int pp = din.readInt();
|
||||
final int typeChar = din.read();
|
||||
final Type type;
|
||||
switch(typeChar) {
|
||||
case 'L': type = Type.OBJECT; break;
|
||||
case 'D': type = Type.NUMBER; break;
|
||||
case 'J': type = Type.LONG; break;
|
||||
default: throw new AssertionError();
|
||||
}
|
||||
map.put(pp, type);
|
||||
}
|
||||
return map;
|
||||
return Type.readTypeMap(din);
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
@ -276,7 +246,7 @@ public final class OptimisticTypesPersistence {
|
||||
private static String getVersionDirName() throws Exception {
|
||||
final URL url = OptimisticTypesPersistence.class.getResource("");
|
||||
final String protocol = url.getProtocol();
|
||||
if(protocol.equals("jar")) {
|
||||
if (protocol.equals("jar")) {
|
||||
// Normal deployment: nashorn.jar
|
||||
final String jarUrlFile = url.getFile();
|
||||
final String filePath = jarUrlFile.substring(0, jarUrlFile.indexOf('!'));
|
||||
@ -310,12 +280,12 @@ public final class OptimisticTypesPersistence {
|
||||
for(final File f: dir.listFiles()) {
|
||||
if(f.getName().endsWith(".class")) {
|
||||
final long lastModified = f.lastModified();
|
||||
if(lastModified > currentMax) {
|
||||
if (lastModified > currentMax) {
|
||||
currentMax = lastModified;
|
||||
}
|
||||
} else if(f.isDirectory()) {
|
||||
} else if (f.isDirectory()) {
|
||||
final long lastModified = getLastModifiedClassFile(f, currentMax);
|
||||
if(lastModified > currentMax) {
|
||||
if (lastModified > currentMax) {
|
||||
currentMax = lastModified;
|
||||
}
|
||||
}
|
||||
@ -325,7 +295,7 @@ public final class OptimisticTypesPersistence {
|
||||
|
||||
private static Object[] createLockArray() {
|
||||
final Object[] lockArray = new Object[Runtime.getRuntime().availableProcessors() * 2];
|
||||
for(int i = 0; i < lockArray.length; ++i) {
|
||||
for (int i = 0; i < lockArray.length; ++i) {
|
||||
lockArray[i] = new Object();
|
||||
}
|
||||
return lockArray;
|
||||
|
||||
@ -48,10 +48,15 @@ import static jdk.internal.org.objectweb.asm.Opcodes.T_INT;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.T_LONG;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.CallSite;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import jdk.internal.org.objectweb.asm.Handle;
|
||||
@ -203,6 +208,20 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
return jdk.internal.org.objectweb.asm.Type.getMethodDescriptor(getInternalType(returnType), itypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a character representing {@code type} in a method signature.
|
||||
*
|
||||
* @param type parameter type
|
||||
* @return descriptor character
|
||||
*/
|
||||
public static char getShortSignatureDescriptor(final Type type) {
|
||||
// Use 'Z' for boolean parameters as we need to distinguish from int
|
||||
if (type instanceof BooleanType) {
|
||||
return 'Z';
|
||||
}
|
||||
return type.getBytecodeStackType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type for an internal type, package private - do not use
|
||||
* outside code gen
|
||||
@ -275,6 +294,64 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a map of {@code int} to {@code Type} to an output stream. This is used to store deoptimization state.
|
||||
*
|
||||
* @param typeMap the type map
|
||||
* @param output data output
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void writeTypeMap(Map<Integer, Type> typeMap, final DataOutput output) throws IOException {
|
||||
if (typeMap == null) {
|
||||
output.writeInt(0);
|
||||
} else {
|
||||
output.writeInt(typeMap.size());
|
||||
for(Map.Entry<Integer, Type> e: typeMap.entrySet()) {
|
||||
output.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();
|
||||
}
|
||||
output.writeByte(typeChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a map of {@code int} to {@code Type} from an input stream. This is used to store deoptimization state.
|
||||
*
|
||||
* @param input data input
|
||||
* @return type map
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Map<Integer, Type> readTypeMap(DataInput input) throws IOException {
|
||||
final int size = input.readInt();
|
||||
if (size == 0) {
|
||||
return null;
|
||||
}
|
||||
final Map<Integer, Type> map = new TreeMap<>();
|
||||
for(int i = 0; i < size; ++i) {
|
||||
final int pp = input.readInt();
|
||||
final int typeChar = input.readByte();
|
||||
final Type type;
|
||||
switch(typeChar) {
|
||||
case 'L': type = Type.OBJECT; break;
|
||||
case 'D': type = Type.NUMBER; break;
|
||||
case 'J': type = Type.LONG; break;
|
||||
default: throw new AssertionError();
|
||||
}
|
||||
map.put(pp, type);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
static jdk.internal.org.objectweb.asm.Type getInternalType(final String className) {
|
||||
return jdk.internal.org.objectweb.asm.Type.getType(className);
|
||||
}
|
||||
|
||||
@ -30,7 +30,6 @@ import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.CompileUnit;
|
||||
@ -150,9 +149,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
/** Function flags. */
|
||||
private final int flags;
|
||||
|
||||
/** //@ sourceURL or //# sourceURL for program function nodes */
|
||||
private final String sourceURL;
|
||||
|
||||
/** Line number of function start */
|
||||
private final int lineNumber;
|
||||
|
||||
@ -269,7 +265,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* @param parameters parameter list
|
||||
* @param kind kind of function as in {@link FunctionNode.Kind}
|
||||
* @param flags initial flags
|
||||
* @param sourceURL sourceURL specified in script (optional)
|
||||
*/
|
||||
public FunctionNode(
|
||||
final Source source,
|
||||
@ -283,8 +278,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
final String name,
|
||||
final List<IdentNode> parameters,
|
||||
final FunctionNode.Kind kind,
|
||||
final int flags,
|
||||
final String sourceURL) {
|
||||
final int flags) {
|
||||
super(token, finish);
|
||||
|
||||
this.source = source;
|
||||
@ -300,7 +294,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this.compilationState = EnumSet.of(CompilationState.INITIALIZED);
|
||||
this.declaredSymbols = new HashSet<>();
|
||||
this.flags = flags;
|
||||
this.sourceURL = sourceURL;
|
||||
this.compileUnit = null;
|
||||
this.body = null;
|
||||
this.thisProperties = 0;
|
||||
@ -311,7 +304,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
final FunctionNode functionNode,
|
||||
final long lastToken,
|
||||
final int flags,
|
||||
final String sourceURL,
|
||||
final String name,
|
||||
final Type returnType,
|
||||
final CompileUnit compileUnit,
|
||||
@ -324,7 +316,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
|
||||
this.lineNumber = functionNode.lineNumber;
|
||||
this.flags = flags;
|
||||
this.sourceURL = sourceURL;
|
||||
this.name = name;
|
||||
this.returnType = returnType;
|
||||
this.compileUnit = compileUnit;
|
||||
@ -384,56 +375,18 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* @return name for the script source
|
||||
*/
|
||||
public String getSourceName() {
|
||||
return getSourceName(source, sourceURL);
|
||||
return getSourceName(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static source name getter
|
||||
*
|
||||
* @param source
|
||||
* @param sourceURL
|
||||
* @param source the source
|
||||
* @return source name
|
||||
*/
|
||||
public static String getSourceName(final Source source, final String sourceURL) {
|
||||
return sourceURL != null ? sourceURL : source.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* get the sourceURL
|
||||
* @return the sourceURL
|
||||
*/
|
||||
public String getSourceURL() {
|
||||
return sourceURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sourceURL
|
||||
*
|
||||
* @param lc lexical context
|
||||
* @param newSourceURL source url string to set
|
||||
* @return function node or a new one if state was changed
|
||||
*/
|
||||
public FunctionNode setSourceURL(final LexicalContext lc, final String newSourceURL) {
|
||||
if (Objects.equals(sourceURL, newSourceURL)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return Node.replaceInLexicalContext(
|
||||
lc,
|
||||
this,
|
||||
new FunctionNode(
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
newSourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
compilationState,
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
public static String getSourceName(final Source source) {
|
||||
final String explicitURL = source.getExplicitURL();
|
||||
return explicitURL != null ? explicitURL : source.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -504,7 +457,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
@ -576,7 +528,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
@ -733,7 +684,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
(body.needsScope() ?
|
||||
FunctionNode.HAS_SCOPE_BLOCK :
|
||||
0),
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
@ -829,7 +779,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
@ -890,7 +839,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
@ -926,7 +874,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
@ -992,7 +939,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
@ -1071,7 +1017,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
type,
|
||||
compileUnit,
|
||||
@ -1118,7 +1063,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
@ -1174,7 +1118,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this,
|
||||
lastToken,
|
||||
flags,
|
||||
sourceURL,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
|
||||
@ -91,9 +91,6 @@ public abstract class AbstractParser {
|
||||
/** What should line numbers be counted from? */
|
||||
protected final int lineOffset;
|
||||
|
||||
/** //@ sourceURL or //# sourceURL */
|
||||
protected String sourceURL;
|
||||
|
||||
/**
|
||||
* Construct a parser.
|
||||
*
|
||||
@ -182,7 +179,7 @@ public abstract class AbstractParser {
|
||||
// currently only @sourceURL=foo supported
|
||||
private void checkDirectiveComment() {
|
||||
// if already set, ignore this one
|
||||
if (sourceURL != null) {
|
||||
if (source.getExplicitURL() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -190,7 +187,7 @@ public abstract class AbstractParser {
|
||||
final int len = comment.length();
|
||||
// 4 characters for directive comment marker //@\s or //#\s
|
||||
if (len > 4 && comment.substring(4).startsWith(SOURCE_URL_PREFIX)) {
|
||||
sourceURL = comment.substring(4 + SOURCE_URL_PREFIX.length());
|
||||
source.setExplicitURL(comment.substring(4 + SOURCE_URL_PREFIX.length()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -477,8 +477,7 @@ loop:
|
||||
name,
|
||||
parameters,
|
||||
kind,
|
||||
flags,
|
||||
sourceURL);
|
||||
flags);
|
||||
|
||||
lc.push(functionNode);
|
||||
// Create new block, and just put it on the context stack, restoreFunctionNode() will associate it with the
|
||||
@ -702,10 +701,6 @@ loop:
|
||||
|
||||
script = restoreFunctionNode(script, token); //commit code
|
||||
script = script.setBody(lc, script.getBody().setNeedsScope(lc));
|
||||
// user may have directive comment to set sourceURL
|
||||
if (sourceURL != null) {
|
||||
script = script.setSourceURL(lc, sourceURL);
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
@ -136,16 +136,16 @@ public class AccessorProperty extends Property {
|
||||
}
|
||||
|
||||
/** Seed getter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */
|
||||
private transient MethodHandle primitiveGetter;
|
||||
transient MethodHandle primitiveGetter;
|
||||
|
||||
/** Seed setter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */
|
||||
private transient MethodHandle primitiveSetter;
|
||||
transient MethodHandle primitiveSetter;
|
||||
|
||||
/** Seed getter for the Object version of this field */
|
||||
private transient MethodHandle objectGetter;
|
||||
transient MethodHandle objectGetter;
|
||||
|
||||
/** Seed setter for the Object version of this field */
|
||||
private transient MethodHandle objectSetter;
|
||||
transient MethodHandle objectSetter;
|
||||
|
||||
/**
|
||||
* Current type of this object, in object only mode, this is an Object.class. In dual-fields mode
|
||||
@ -185,10 +185,10 @@ public class AccessorProperty extends Property {
|
||||
* @param key the property key
|
||||
* @param flags the property flags
|
||||
* @param slot spill slot
|
||||
* @param objectGetter
|
||||
* @param objectSetter
|
||||
* @param primitiveGetter
|
||||
* @param primitiveSetter
|
||||
* @param primitiveGetter primitive getter
|
||||
* @param primitiveSetter primitive setter
|
||||
* @param objectGetter object getter
|
||||
* @param objectSetter object setter
|
||||
*/
|
||||
protected AccessorProperty(
|
||||
final String key,
|
||||
@ -255,7 +255,7 @@ public class AccessorProperty extends Property {
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal ACCESS PROPERTY constructor given a structure glass.
|
||||
* Normal ACCESS PROPERTY constructor given a structure class.
|
||||
* Constructor for dual field AccessorPropertys.
|
||||
*
|
||||
* @param key property key
|
||||
@ -267,6 +267,7 @@ public class AccessorProperty extends Property {
|
||||
super(key, flags, slot);
|
||||
|
||||
initGetterSetter(structure);
|
||||
initializeType();
|
||||
}
|
||||
|
||||
private void initGetterSetter(final Class<?> structure) {
|
||||
@ -291,8 +292,6 @@ public class AccessorProperty extends Property {
|
||||
objectSetter = gs.objectSetters[slot];
|
||||
primitiveSetter = gs.primitiveSetters[slot];
|
||||
}
|
||||
|
||||
initializeType();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,8 +411,8 @@ public class AccessorProperty extends Property {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLongValue(final ScriptObject self, final ScriptObject owner) {
|
||||
@Override
|
||||
public long getLongValue(final ScriptObject self, final ScriptObject owner) {
|
||||
try {
|
||||
return (long)getGetter(long.class).invokeExact((Object)self);
|
||||
} catch (final Error | RuntimeException e) {
|
||||
@ -531,12 +530,13 @@ public class AccessorProperty extends Property {
|
||||
|
||||
@Override
|
||||
void initMethodHandles(final Class<?> structure) {
|
||||
// sanity check for structure class
|
||||
if (!ScriptObject.class.isAssignableFrom(structure) || !StructureLoader.isStructureClass(structure.getName())) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (!isSpill()) {
|
||||
initGetterSetter(structure);
|
||||
}
|
||||
// this method is overridden in SpillProperty
|
||||
assert !isSpill();
|
||||
initGetterSetter(structure);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -92,5 +92,14 @@ public interface CodeInstaller<T> {
|
||||
* @param classBytes map of class names to class bytes
|
||||
* @param constants constants array
|
||||
*/
|
||||
public void storeCompiledScript(Source source, String mainClassName, Map<String, byte[]> classBytes, Object[] constants);
|
||||
public void storeScript(String cacheKey, Source source, String mainClassName, Map<String, byte[]> classBytes,
|
||||
Map<Integer, FunctionInitializer> initializers, Object[] constants, int compilationId);
|
||||
|
||||
/**
|
||||
* Load a previously compiled script
|
||||
* @param source the script source
|
||||
* @param functionKey the function id and signature
|
||||
* @return compiled script data
|
||||
*/
|
||||
public StoredScript loadScript(Source source, String functionKey);
|
||||
}
|
||||
|
||||
@ -25,6 +25,11 @@
|
||||
|
||||
package jdk.nashorn.internal.runtime;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.runtime.logging.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.logging.Loggable;
|
||||
import jdk.nashorn.internal.runtime.logging.Logger;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
@ -42,21 +47,22 @@ import java.util.Map;
|
||||
/**
|
||||
* A code cache for persistent caching of compiled scripts.
|
||||
*/
|
||||
final class CodeStore {
|
||||
@Logger(name="codestore")
|
||||
final class CodeStore implements Loggable {
|
||||
|
||||
private final File dir;
|
||||
private final int minSize;
|
||||
private final DebugLogger log;
|
||||
|
||||
// Default minimum size for storing a compiled script class
|
||||
private final static int DEFAULT_MIN_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param path directory to store code in
|
||||
* @throws IOException
|
||||
*/
|
||||
public CodeStore(final String path) throws IOException {
|
||||
this(path, DEFAULT_MIN_SIZE);
|
||||
public CodeStore(final Context context, final String path) throws IOException {
|
||||
this(context, path, DEFAULT_MIN_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,9 +71,20 @@ final class CodeStore {
|
||||
* @param minSize minimum file size for caching scripts
|
||||
* @throws IOException
|
||||
*/
|
||||
public CodeStore(final String path, final int minSize) throws IOException {
|
||||
public CodeStore(final Context context, final String path, final int minSize) throws IOException {
|
||||
this.dir = checkDirectory(path);
|
||||
this.minSize = minSize;
|
||||
this.log = initLogger(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger initLogger(Context context) {
|
||||
return context.getLogger(getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger getLogger() {
|
||||
return log;
|
||||
}
|
||||
|
||||
private static File checkDirectory(final String path) throws IOException {
|
||||
@ -77,11 +94,11 @@ final class CodeStore {
|
||||
public File run() throws IOException {
|
||||
final File dir = new File(path).getAbsoluteFile();
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
throw new IOException("Could not create directory: " + dir);
|
||||
throw new IOException("Could not create directory: " + dir.getPath());
|
||||
} else if (!dir.isDirectory()) {
|
||||
throw new IOException("Not a directory: " + dir);
|
||||
throw new IOException("Not a directory: " + dir.getPath());
|
||||
} else if (!dir.canRead() || !dir.canWrite()) {
|
||||
throw new IOException("Directory not readable or writable: " + dir);
|
||||
throw new IOException("Directory not readable or writable: " + dir.getPath());
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
@ -91,69 +108,85 @@ final class CodeStore {
|
||||
}
|
||||
}
|
||||
|
||||
private File getCacheFile(final Source source, final String functionKey) {
|
||||
return new File(dir, source.getDigest() + '-' + functionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a string representing the function with {@code functionId} and {@code paramTypes}.
|
||||
* @param functionId function id
|
||||
* @param paramTypes parameter types
|
||||
* @return a string representing the function
|
||||
*/
|
||||
public static String getCacheKey(final int functionId, final Type[] paramTypes) {
|
||||
final StringBuilder b = new StringBuilder().append(functionId);
|
||||
if(paramTypes != null && paramTypes.length > 0) {
|
||||
b.append('-');
|
||||
for(final Type t: paramTypes) {
|
||||
b.append(Type.getShortSignatureDescriptor(t));
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a compiled script from the cache, or null if it isn't found.
|
||||
*
|
||||
* @param source the source
|
||||
* @return the compiled script or null
|
||||
* @throws IOException
|
||||
* @throws ClassNotFoundException
|
||||
* @param functionKey the function key
|
||||
* @return the stored script or null
|
||||
*/
|
||||
public CompiledScript getScript(final Source source) throws IOException, ClassNotFoundException {
|
||||
public StoredScript loadScript(final Source source, final String functionKey) {
|
||||
if (source.getLength() < minSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final File file = new File(dir, source.getDigest());
|
||||
final File file = getCacheFile(source, functionKey);
|
||||
|
||||
try {
|
||||
return AccessController.doPrivileged(new PrivilegedExceptionAction<CompiledScript>() {
|
||||
return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
|
||||
@Override
|
||||
public CompiledScript run() throws IOException, ClassNotFoundException {
|
||||
public StoredScript run() throws IOException, ClassNotFoundException {
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
|
||||
final CompiledScript compiledScript = (CompiledScript) in.readObject();
|
||||
compiledScript.setSource(source);
|
||||
return compiledScript;
|
||||
final StoredScript storedScript = (StoredScript) in.readObject();
|
||||
getLogger().info("loaded ", source, "-", functionKey);
|
||||
return storedScript;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (final PrivilegedActionException e) {
|
||||
final Exception ex = e.getException();
|
||||
if (ex instanceof IOException) {
|
||||
throw (IOException) ex;
|
||||
} else if (ex instanceof ClassNotFoundException) {
|
||||
throw (ClassNotFoundException) ex;
|
||||
}
|
||||
throw (new RuntimeException(ex));
|
||||
getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a compiled script in the cache.
|
||||
*
|
||||
* @param functionKey the function key
|
||||
* @param source the source
|
||||
* @param mainClassName the main class name
|
||||
* @param classBytes a map of class bytes
|
||||
* @param constants the constants array
|
||||
* @throws IOException
|
||||
*/
|
||||
public void putScript(final Source source, final String mainClassName, final Map<String, byte[]> classBytes, final Object[] constants)
|
||||
throws IOException {
|
||||
public void storeScript(final String functionKey, final Source source, final String mainClassName, final Map<String, byte[]> classBytes,
|
||||
final Map<Integer, FunctionInitializer> initializers, final Object[] constants, final int compilationId) {
|
||||
if (source.getLength() < minSize) {
|
||||
return;
|
||||
}
|
||||
for (final Object constant : constants) {
|
||||
// Make sure all constant data is serializable
|
||||
if (! (constant instanceof Serializable)) {
|
||||
getLogger().warning("cannot store ", source, " non serializable constant ", constant);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final File file = new File(dir, source.getDigest());
|
||||
final CompiledScript script = new CompiledScript(source, mainClassName, classBytes, constants);
|
||||
final File file = getCacheFile(source, functionKey);
|
||||
final StoredScript script = new StoredScript(compilationId, mainClassName, classBytes, initializers, constants);
|
||||
|
||||
try {
|
||||
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
|
||||
@ -162,11 +195,12 @@ final class CodeStore {
|
||||
try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
|
||||
out.writeObject(script);
|
||||
}
|
||||
getLogger().info("stored ", source, "-", functionKey);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (final PrivilegedActionException e) {
|
||||
throw (IOException) e.getException();
|
||||
getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ import java.util.logging.Level;
|
||||
import jdk.internal.dynalink.linker.GuardedInvocation;
|
||||
import jdk.nashorn.internal.codegen.Compiler;
|
||||
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
|
||||
import jdk.nashorn.internal.codegen.TypeMap;
|
||||
import jdk.nashorn.internal.codegen.types.ArrayType;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
@ -72,6 +73,7 @@ final class CompiledFunction {
|
||||
private MethodHandle constructor;
|
||||
private OptimismInfo optimismInfo;
|
||||
private final int flags; // from FunctionNode
|
||||
private final MethodType callSiteType;
|
||||
|
||||
CompiledFunction(final MethodHandle invoker) {
|
||||
this(invoker, null);
|
||||
@ -82,19 +84,20 @@ final class CompiledFunction {
|
||||
}
|
||||
|
||||
CompiledFunction(final MethodHandle invoker, final MethodHandle constructor) {
|
||||
this(invoker, constructor, 0, DebugLogger.DISABLED_LOGGER);
|
||||
this(invoker, constructor, 0, null, DebugLogger.DISABLED_LOGGER);
|
||||
}
|
||||
|
||||
CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final int flags, final DebugLogger log) {
|
||||
CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final int flags, final MethodType callSiteType, final DebugLogger log) {
|
||||
this.invoker = invoker;
|
||||
this.constructor = constructor;
|
||||
this.flags = flags;
|
||||
this.callSiteType = callSiteType;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData,
|
||||
final Map<Integer, Type> invalidatedProgramPoints, final int flags) {
|
||||
this(invoker, null, flags, functionData.getLogger());
|
||||
final Map<Integer, Type> invalidatedProgramPoints, final MethodType callSiteType, final int flags) {
|
||||
this(invoker, null, flags, callSiteType, functionData.getLogger());
|
||||
if ((flags & FunctionNode.IS_DEOPTIMIZABLE) != 0) {
|
||||
optimismInfo = new OptimismInfo(functionData, invalidatedProgramPoints);
|
||||
} else {
|
||||
@ -127,9 +130,9 @@ final class CompiledFunction {
|
||||
* Returns an invoker method handle for this function. Note that the handle is safely composable in
|
||||
* the sense that you can compose it with other handles using any combinators even if you can't affect call site
|
||||
* invalidation. If this compiled function is non-optimistic, then it returns the same value as
|
||||
* {@link #getInvoker()}. However, if the function is optimistic, then this handle will incur an overhead as it will
|
||||
* add an intermediate internal call site that can relink itself when the function needs to regenerate its code to
|
||||
* always point at the latest generated code version.
|
||||
* {@link #getInvokerOrConstructor(boolean)}. However, if the function is optimistic, then this handle will
|
||||
* incur an overhead as it will add an intermediate internal call site that can relink itself when the function
|
||||
* needs to regenerate its code to always point at the latest generated code version.
|
||||
* @return a guaranteed composable invoker method handle for this function.
|
||||
*/
|
||||
MethodHandle createComposableInvoker() {
|
||||
@ -165,8 +168,6 @@ final class CompiledFunction {
|
||||
* Compose a constructor from an invoker.
|
||||
*
|
||||
* @param invoker invoker
|
||||
* @param needsCallee do we need to pass a callee
|
||||
*
|
||||
* @return the composed constructor
|
||||
*/
|
||||
private static MethodHandle createConstructorFromInvoker(final MethodHandle invoker) {
|
||||
@ -427,6 +428,9 @@ final class CompiledFunction {
|
||||
}
|
||||
|
||||
boolean matchesCallSite(final MethodType callSiteType, final boolean pickVarArg) {
|
||||
if (callSiteType.equals(this.callSiteType)) {
|
||||
return true;
|
||||
}
|
||||
final MethodType type = type();
|
||||
final int fnParamCount = getParamCount(type);
|
||||
final boolean isVarArg = fnParamCount == Integer.MAX_VALUE;
|
||||
@ -719,6 +723,15 @@ final class CompiledFunction {
|
||||
logRecompile("Rest-of compilation [CODE PIPELINE REUSE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
|
||||
final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.COMPILE_FROM_BYTECODE);
|
||||
|
||||
if (effectiveOptInfo.data.usePersistentCodeCache()) {
|
||||
final RecompilableScriptFunctionData data = effectiveOptInfo.data;
|
||||
final int functionNodeId = data.getFunctionNodeId();
|
||||
final TypeMap typeMap = data.typeMap(callSiteType);
|
||||
final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
|
||||
final String cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes);
|
||||
compiler.persistClassInfo(cacheKey, normalFn);
|
||||
}
|
||||
|
||||
FunctionNode fn2 = effectiveOptInfo.reparse();
|
||||
fn2 = compiler.compile(fn2, CompilationPhases.COMPILE_UPTO_BYTECODE);
|
||||
log.info("Done.");
|
||||
@ -751,11 +764,11 @@ final class CompiledFunction {
|
||||
|
||||
private MethodHandle restOfHandle(final OptimismInfo info, final FunctionNode restOfFunction, final boolean canBeDeoptimized) {
|
||||
assert info != null;
|
||||
assert restOfFunction.getCompileUnit().getUnitClassName().indexOf("restOf") != -1;
|
||||
assert restOfFunction.getCompileUnit().getUnitClassName().contains("restOf");
|
||||
final MethodHandle restOf =
|
||||
changeReturnType(
|
||||
info.data.lookupWithExplicitType(
|
||||
restOfFunction,
|
||||
info.data.lookupCodeMethod(
|
||||
restOfFunction.getCompileUnit().getCode(),
|
||||
MH.type(restOfFunction.getReturnType().getTypeClass(),
|
||||
RewriteException.class)),
|
||||
Object.class);
|
||||
|
||||
@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, 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 static jdk.nashorn.internal.lookup.Lookup.MH;
|
||||
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* This is a list of code versions of a function.
|
||||
* The list is sorted in ascending order of generic descriptors
|
||||
*/
|
||||
final class CompiledFunctions {
|
||||
|
||||
private final String name;
|
||||
final LinkedList<CompiledFunction> functions = new LinkedList<>();
|
||||
|
||||
CompiledFunctions(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
void add(final CompiledFunction f) {
|
||||
functions.add(f);
|
||||
}
|
||||
|
||||
void addAll(final CompiledFunctions fs) {
|
||||
functions.addAll(fs.functions);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return functions.isEmpty();
|
||||
}
|
||||
|
||||
int size() {
|
||||
return functions.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return '\'' + name + "' code=" + functions;
|
||||
}
|
||||
|
||||
private static MethodType widen(final MethodType cftype) {
|
||||
final Class<?>[] paramTypes = new Class<?>[cftype.parameterCount()];
|
||||
for (int i = 0; i < cftype.parameterCount(); i++) {
|
||||
paramTypes[i] = cftype.parameterType(i).isPrimitive() ? cftype.parameterType(i) : Object.class;
|
||||
}
|
||||
return MH.type(cftype.returnType(), paramTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to find an apply to call version that fits this callsite.
|
||||
* We cannot just, as in the normal matcher case, return e.g. (Object, Object, int)
|
||||
* for (Object, Object, int, int, int) or we will destroy the semantics and get
|
||||
* a function that, when padded with undefineds, behaves differently
|
||||
* @param type actual call site type
|
||||
* @return apply to call that perfectly fits this callsite or null if none found
|
||||
*/
|
||||
CompiledFunction lookupExactApplyToCall(final MethodType type) {
|
||||
for (final CompiledFunction cf : functions) {
|
||||
if (!cf.isApplyToCall()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final MethodType cftype = cf.type();
|
||||
if (cftype.parameterCount() != type.parameterCount()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (widen(cftype).equals(widen(type))) {
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private CompiledFunction pick(final MethodType callSiteType, final boolean canPickVarArg) {
|
||||
for (final CompiledFunction candidate : functions) {
|
||||
if (candidate.matchesCallSite(callSiteType, false)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compiled function best matching the requested call site method type
|
||||
* @param callSiteType
|
||||
* @param recompilable
|
||||
* @param hasThis
|
||||
* @return
|
||||
*/
|
||||
CompiledFunction best(final MethodType callSiteType, final boolean recompilable) {
|
||||
assert callSiteType.parameterCount() >= 2 : callSiteType; // Must have at least (callee, this)
|
||||
assert callSiteType.parameterType(0).isAssignableFrom(ScriptFunction.class) : callSiteType; // Callee must be assignable from script function
|
||||
|
||||
if (recompilable) {
|
||||
final CompiledFunction candidate = pick(callSiteType, false);
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
return pick(callSiteType, true); //try vararg last
|
||||
}
|
||||
|
||||
CompiledFunction best = null;
|
||||
for(final CompiledFunction candidate: functions) {
|
||||
if(candidate.betterThanFinal(best, callSiteType)) {
|
||||
best = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if functions managed by this {@code CompiledFunctions} require a callee. This method is only safe to
|
||||
* be invoked for a {@code CompiledFunctions} that is not empty. As such, it should only be used from
|
||||
* {@link FinalScriptFunctionData} and not from {@link RecompilableScriptFunctionData}.
|
||||
* @return true if the functions need a callee, false otherwise.
|
||||
*/
|
||||
boolean needsCallee() {
|
||||
final boolean needsCallee = functions.getFirst().needsCallee();
|
||||
assert allNeedCallee(needsCallee);
|
||||
return needsCallee;
|
||||
}
|
||||
|
||||
private boolean allNeedCallee(final boolean needCallee) {
|
||||
for (final CompiledFunction inv : functions) {
|
||||
if(inv.needsCallee() != needCallee) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this CompiledFunctions object belongs to a {@code FinalScriptFunctionData}, get a method type for a generic
|
||||
* invoker. It will either be a vararg type, if any of the contained functions is vararg, or a generic type of the
|
||||
* arity of the largest arity of all functions.
|
||||
* @return the method type for the generic invoker
|
||||
*/
|
||||
MethodType getFinalGenericType() {
|
||||
int max = 0;
|
||||
for(final CompiledFunction fn: functions) {
|
||||
final MethodType t = fn.type();
|
||||
if(ScriptFunctionData.isVarArg(t)) {
|
||||
// 2 for (callee, this, args[])
|
||||
return MethodType.genericMethodType(2, true);
|
||||
}
|
||||
final int paramCount = t.parameterCount() - (ScriptFunctionData.needsCallee(t) ? 1 : 0);
|
||||
if(paramCount > max) {
|
||||
max = paramCount;
|
||||
}
|
||||
}
|
||||
// +1 for callee
|
||||
return MethodType.genericMethodType(max + 1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -164,10 +164,13 @@ public final class Context {
|
||||
public void initialize(final Collection<Class<?>> classes, final Source source, final Object[] constants) {
|
||||
// do these in parallel, this significantly reduces class installation overhead
|
||||
// however - it still means that every thread needs a separate doPrivileged
|
||||
final Global global = currentGlobal.get();
|
||||
classes.parallelStream().forEach(
|
||||
new Consumer<Class<?>>() {
|
||||
@Override
|
||||
public void accept(final Class<?> clazz) {
|
||||
// Global threadlocal may be needed by StructureLoader during in field lookup.
|
||||
currentGlobal.set(global);
|
||||
try {
|
||||
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
|
||||
@Override
|
||||
@ -210,16 +213,21 @@ public final class Context {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeCompiledScript(final Source source, final String mainClassName,
|
||||
final Map<String, byte[]> classBytes, final Object[] constants) {
|
||||
public void storeScript(final String classInfoFile, final Source source, final String mainClassName,
|
||||
final Map<String,byte[]> classBytes, Map<Integer, FunctionInitializer> initializers,
|
||||
final Object[] constants, final int compilationId) {
|
||||
if (context.codeStore != null) {
|
||||
try {
|
||||
context.codeStore.putScript(source, mainClassName, classBytes, constants);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
context.codeStore.storeScript(classInfoFile, source, mainClassName, classBytes, initializers, constants, compilationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredScript loadScript(final Source source, final String functionKey) {
|
||||
if (context.codeStore != null) {
|
||||
return context.codeStore.loadScript(source, functionKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Is Context global debug mode enabled ? */
|
||||
@ -447,7 +455,7 @@ public final class Context {
|
||||
if (env._persistent_cache) {
|
||||
try {
|
||||
final String cacheDir = Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache");
|
||||
codeStore = new CodeStore(cacheDir);
|
||||
codeStore = new CodeStore(this, cacheDir);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException("Error initializing code cache", e);
|
||||
}
|
||||
@ -1080,19 +1088,16 @@ public final class Context {
|
||||
return script;
|
||||
}
|
||||
|
||||
CompiledScript compiledScript = null;
|
||||
StoredScript storedScript = null;
|
||||
FunctionNode functionNode = null;
|
||||
final boolean useCodeStore = env._persistent_cache && !env._parse_only && !env._optimistic_types;
|
||||
final String cacheKey = useCodeStore ? CodeStore.getCacheKey(0, null) : null;
|
||||
|
||||
if (!env._parse_only && codeStore != null) {
|
||||
try {
|
||||
compiledScript = codeStore.getScript(source);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
getLogger(Compiler.class).warning("Error loading ", source, " from cache: ", e);
|
||||
// Fall back to normal compilation
|
||||
}
|
||||
if (useCodeStore) {
|
||||
storedScript = codeStore.loadScript(source, cacheKey);
|
||||
}
|
||||
|
||||
if (compiledScript == null) {
|
||||
if (storedScript == null) {
|
||||
functionNode = new Parser(env, source, errMan, strict, getLogger(Parser.class)).parse();
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
@ -1117,7 +1122,7 @@ public final class Context {
|
||||
final CodeSource cs = new CodeSource(url, (CodeSigner[])null);
|
||||
final CodeInstaller<ScriptEnvironment> installer = new ContextCodeInstaller(this, loader, cs);
|
||||
|
||||
if (functionNode != null) {
|
||||
if (storedScript == null) {
|
||||
final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL;
|
||||
|
||||
final Compiler compiler = new Compiler(
|
||||
@ -1125,12 +1130,14 @@ public final class Context {
|
||||
env,
|
||||
installer,
|
||||
source,
|
||||
functionNode.getSourceURL(),
|
||||
strict | functionNode.isStrict());
|
||||
|
||||
script = compiler.compile(functionNode, phases).getRootClass();
|
||||
final FunctionNode compiledFunction = compiler.compile(functionNode, phases);
|
||||
script = compiledFunction.getRootClass();
|
||||
compiler.persistClassInfo(cacheKey, compiledFunction);
|
||||
} else {
|
||||
script = install(compiledScript, installer);
|
||||
Compiler.updateCompilationId(storedScript.getCompilationId());
|
||||
script = install(storedScript, source, installer);
|
||||
}
|
||||
|
||||
cacheClass(source, script);
|
||||
@ -1155,27 +1162,26 @@ public final class Context {
|
||||
return uniqueScriptId.getAndIncrement();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Install a previously compiled class from the code cache.
|
||||
*
|
||||
* @param compiledScript cached script containing class bytes and constants
|
||||
* @param storedScript cached script containing class bytes and constants
|
||||
* @return main script class
|
||||
*/
|
||||
private static Class<?> install(final CompiledScript compiledScript, final CodeInstaller<ScriptEnvironment> installer) {
|
||||
private static Class<?> install(final StoredScript storedScript, final Source source, final CodeInstaller<ScriptEnvironment> installer) {
|
||||
|
||||
final Map<String, Class<?>> installedClasses = new HashMap<>();
|
||||
final Source source = compiledScript.getSource();
|
||||
final Object[] constants = compiledScript.getConstants();
|
||||
final String rootClassName = compiledScript.getMainClassName();
|
||||
final byte[] rootByteCode = compiledScript.getClassBytes().get(rootClassName);
|
||||
final Class<?> rootClass = installer.install(rootClassName, rootByteCode);
|
||||
final Object[] constants = storedScript.getConstants();
|
||||
final String mainClassName = storedScript.getMainClassName();
|
||||
final byte[] mainClassBytes = storedScript.getClassBytes().get(mainClassName);
|
||||
final Class<?> mainClass = installer.install(mainClassName, mainClassBytes);
|
||||
final Map<Integer, FunctionInitializer> initialzers = storedScript.getInitializers();
|
||||
|
||||
installedClasses.put(rootClassName, rootClass);
|
||||
installedClasses.put(mainClassName, mainClass);
|
||||
|
||||
for (final Map.Entry<String, byte[]> entry : compiledScript.getClassBytes().entrySet()) {
|
||||
for (final Map.Entry<String, byte[]> entry : storedScript.getClassBytes().entrySet()) {
|
||||
final String className = entry.getKey();
|
||||
if (className.equals(rootClassName)) {
|
||||
if (className.equals(mainClassName)) {
|
||||
continue;
|
||||
}
|
||||
final byte[] code = entry.getValue();
|
||||
@ -1187,11 +1193,17 @@ public final class Context {
|
||||
|
||||
for (final Object constant : constants) {
|
||||
if (constant instanceof RecompilableScriptFunctionData) {
|
||||
((RecompilableScriptFunctionData) constant).initTransients(source, installer);
|
||||
final RecompilableScriptFunctionData data = (RecompilableScriptFunctionData) constant;
|
||||
data.initTransients(source, installer);
|
||||
if (initialzers != null) {
|
||||
final FunctionInitializer initializer = initialzers.get(data.getFunctionNodeId());
|
||||
initializer.setCode(installedClasses.get(initializer.getClassName()));
|
||||
data.initializeCode(initializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rootClass;
|
||||
return mainClass;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -27,6 +27,7 @@ package jdk.nashorn.internal.runtime;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is a subclass that represents a script function that may not be regenerated.
|
||||
@ -44,10 +45,10 @@ final class FinalScriptFunctionData extends ScriptFunctionData {
|
||||
* @param functions precompiled code
|
||||
* @param flags {@link ScriptFunctionData} flags
|
||||
*/
|
||||
FinalScriptFunctionData(final String name, final int arity, final CompiledFunctions functions, final int flags) {
|
||||
FinalScriptFunctionData(final String name, final int arity, final List<CompiledFunction> functions, final int flags) {
|
||||
super(name, arity, flags);
|
||||
assert !functions.needsCallee();
|
||||
code.addAll(functions);
|
||||
assert !needsCallee();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,8 +77,19 @@ final class FinalScriptFunctionData extends ScriptFunctionData {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean needsCallee() {
|
||||
return code.needsCallee();
|
||||
protected boolean needsCallee() {
|
||||
final boolean needsCallee = code.getFirst().needsCallee();
|
||||
assert allNeedCallee(needsCallee);
|
||||
return needsCallee;
|
||||
}
|
||||
|
||||
private boolean allNeedCallee(final boolean needCallee) {
|
||||
for (final CompiledFunction inv : code) {
|
||||
if(inv.needsCallee() != needCallee) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -86,7 +98,20 @@ final class FinalScriptFunctionData extends ScriptFunctionData {
|
||||
// actually correct for lots of built-ins. E.g. ECMAScript 5.1 section 15.5.3.2 prescribes that
|
||||
// Script.fromCharCode([char0[, char1[, ...]]]) has a declared arity of 1 even though it's a variable arity
|
||||
// method.
|
||||
return code.getFinalGenericType();
|
||||
int max = 0;
|
||||
for(final CompiledFunction fn: code) {
|
||||
final MethodType t = fn.type();
|
||||
if(ScriptFunctionData.isVarArg(t)) {
|
||||
// 2 for (callee, this, args[])
|
||||
return MethodType.genericMethodType(2, true);
|
||||
}
|
||||
final int paramCount = t.parameterCount() - (ScriptFunctionData.needsCallee(t) ? 1 : 0);
|
||||
if(paramCount > max) {
|
||||
max = paramCount;
|
||||
}
|
||||
}
|
||||
// +1 for callee
|
||||
return MethodType.genericMethodType(max + 1);
|
||||
}
|
||||
|
||||
private void addInvoker(final MethodHandle mh) {
|
||||
|
||||
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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 jdk.nashorn.internal.codegen.CompileUnit;
|
||||
import jdk.nashorn.internal.codegen.FunctionSignature;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Class that contains information allowing us to look up a method handle implementing a JavaScript function
|
||||
* from a generated class. This is used both for code coming from codegen and for persistent serialized code.
|
||||
*/
|
||||
public final class FunctionInitializer implements Serializable {
|
||||
|
||||
private final String className;
|
||||
private final MethodType methodType;
|
||||
private final int flags;
|
||||
private transient Map<Integer, Type> invalidatedProgramPoints;
|
||||
private transient Class<?> code;
|
||||
|
||||
private static final long serialVersionUID = -5420835725902966692L;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param functionNode the function node
|
||||
*/
|
||||
public FunctionInitializer(final FunctionNode functionNode) {
|
||||
this(functionNode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param functionNode the function node
|
||||
* @param invalidatedProgramPoints invalidated program points
|
||||
*/
|
||||
public FunctionInitializer(final FunctionNode functionNode, final Map<Integer, Type> invalidatedProgramPoints) {
|
||||
this.className = functionNode.getCompileUnit().getUnitClassName();
|
||||
this.methodType = new FunctionSignature(functionNode).getMethodType();
|
||||
this.flags = functionNode.getFlags();
|
||||
this.invalidatedProgramPoints = invalidatedProgramPoints;
|
||||
|
||||
final CompileUnit cu = functionNode.getCompileUnit();
|
||||
if (cu != null) {
|
||||
this.code = cu.getCode();
|
||||
}
|
||||
|
||||
assert className != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the class implementing the function.
|
||||
*
|
||||
* @return the class name
|
||||
*/
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the method implementing the function.
|
||||
*
|
||||
* @return the method type
|
||||
*/
|
||||
public MethodType getMethodType() {
|
||||
return methodType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the function flags.
|
||||
*
|
||||
* @return function flags
|
||||
*/
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class implementing the function.
|
||||
*
|
||||
* @return the class
|
||||
*/
|
||||
public Class<?> getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the class implementing the function
|
||||
* @param code the class
|
||||
*/
|
||||
public void setCode(Class<?> code) {
|
||||
// Make sure code has not been set and has expected class name
|
||||
if (this.code != null) {
|
||||
throw new IllegalStateException("code already set");
|
||||
}
|
||||
assert className.equals(code.getTypeName().replace('.', '/')) : "unexpected class name";
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map of invalidated program points.
|
||||
*
|
||||
* @return invalidated program points
|
||||
*/
|
||||
public Map<Integer, Type> getInvalidatedProgramPoints() {
|
||||
return invalidatedProgramPoints;
|
||||
}
|
||||
|
||||
private void writeObject(final ObjectOutputStream out) throws IOException {
|
||||
out.defaultWriteObject();
|
||||
Type.writeTypeMap(invalidatedProgramPoints, out);
|
||||
}
|
||||
|
||||
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
invalidatedProgramPoints = Type.readTypeMap(in);
|
||||
}
|
||||
}
|
||||
@ -32,13 +32,13 @@ import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import jdk.internal.dynalink.support.NameCodec;
|
||||
import jdk.nashorn.internal.codegen.CompileUnit;
|
||||
import jdk.nashorn.internal.codegen.Compiler;
|
||||
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
@ -73,11 +73,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
|
||||
private final String functionName;
|
||||
|
||||
// TODO: try to eliminate the need for this somehow, either by allowing Source to change its name, allowing a
|
||||
// function to internally replace its Source with one of a different name, or storing this additional field in the
|
||||
// Source object.
|
||||
private final String sourceURL;
|
||||
|
||||
/** The line number where this function begins. */
|
||||
private final int lineNumber;
|
||||
|
||||
@ -128,7 +123,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
* @param allocatorClassName name of our allocator class, will be looked up dynamically if used as a constructor
|
||||
* @param allocatorMap allocator map to seed instances with, when constructing
|
||||
* @param nestedFunctions nested function map
|
||||
* @param sourceURL source URL
|
||||
* @param externalScopeDepths external scope depths
|
||||
* @param internalSymbols internal symbols to method, defined in its scope
|
||||
*/
|
||||
@ -138,7 +132,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
final String allocatorClassName,
|
||||
final PropertyMap allocatorMap,
|
||||
final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
|
||||
final String sourceURL,
|
||||
final Map<String, Integer> externalScopeDepths,
|
||||
final Set<String> internalSymbols) {
|
||||
|
||||
@ -155,7 +148,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
this.source = functionNode.getSource();
|
||||
this.token = tokenFor(functionNode);
|
||||
this.installer = installer;
|
||||
this.sourceURL = sourceURL;
|
||||
this.allocatorClassName = allocatorClassName;
|
||||
this.allocatorMap = allocatorMap;
|
||||
this.nestedFunctions = nestedFunctions;
|
||||
@ -366,7 +358,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition, Token.descLength(token), true);
|
||||
// Parser generates a program AST even if we're recompiling a single function, so when we are only recompiling a
|
||||
// single function, extract it from the program.
|
||||
return (isProgram ? program : extractFunctionFromScript(program)).setName(null, functionName).setSourceURL(null, sourceURL);
|
||||
return (isProgram ? program : extractFunctionFromScript(program)).setName(null, functionName);
|
||||
}
|
||||
|
||||
TypeMap typeMap(final MethodType fnCallSiteType) {
|
||||
@ -395,18 +387,18 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints,
|
||||
final int[] continuationEntryPoints) {
|
||||
final TypeMap typeMap = typeMap(actualCallSiteType);
|
||||
final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, typeMap == null ? null : typeMap.getParameterTypes(functionNodeId));
|
||||
final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
|
||||
final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, paramTypes);
|
||||
final Context context = Context.getContextTrusted();
|
||||
return new Compiler(
|
||||
context,
|
||||
context.getEnv(),
|
||||
installer,
|
||||
functionNode.getSource(), // source
|
||||
functionNode.getSourceURL(),
|
||||
isStrict() | functionNode.isStrict(), // is strict
|
||||
true, // is on demand
|
||||
this, // compiledFunction, i.e. this RecompilableScriptFunctionData
|
||||
typeMap(actualCallSiteType), // type map
|
||||
typeMap, // type map
|
||||
getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points
|
||||
typeInformationFile,
|
||||
continuationEntryPoints, // continuation entry points
|
||||
@ -431,7 +423,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>();
|
||||
}
|
||||
|
||||
private TypeSpecializedFunction compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
|
||||
private FunctionInitializer compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist) {
|
||||
// We're creating an empty script object for holding local variables. AssignSymbols will populate it with
|
||||
// explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and
|
||||
// CompilationEnvironment#declareLocalSymbol()).
|
||||
@ -440,21 +432,79 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
log.info("Type specialization of '", functionName, "' signature: ", actualCallSiteType);
|
||||
}
|
||||
|
||||
final boolean persistentCache = usePersistentCodeCache() && persist;
|
||||
String cacheKey = null;
|
||||
if (persistentCache) {
|
||||
final TypeMap typeMap = typeMap(actualCallSiteType);
|
||||
final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
|
||||
cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes);
|
||||
final StoredScript script = installer.loadScript(source, cacheKey);
|
||||
|
||||
if (script != null) {
|
||||
Compiler.updateCompilationId(script.getCompilationId());
|
||||
return install(script);
|
||||
}
|
||||
}
|
||||
|
||||
final FunctionNode fn = reparse();
|
||||
final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
|
||||
|
||||
final FunctionNode compiledFn = compiler.compile(fn, CompilationPhases.COMPILE_ALL);
|
||||
return new TypeSpecializedFunction(compiledFn, compiler.getInvalidatedProgramPoints());
|
||||
|
||||
if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
|
||||
compiler.persistClassInfo(cacheKey, compiledFn);
|
||||
}
|
||||
return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());
|
||||
}
|
||||
|
||||
private static class TypeSpecializedFunction {
|
||||
private final FunctionNode fn;
|
||||
private final Map<Integer, Type> invalidatedProgramPoints;
|
||||
|
||||
TypeSpecializedFunction(final FunctionNode fn, final Map<Integer, Type> invalidatedProgramPoints) {
|
||||
this.fn = fn;
|
||||
this.invalidatedProgramPoints = invalidatedProgramPoints;
|
||||
/**
|
||||
* Install this script using the given {@code installer}.
|
||||
*
|
||||
* @param script the compiled script
|
||||
* @return the function initializer
|
||||
*/
|
||||
private FunctionInitializer install(final StoredScript script) {
|
||||
|
||||
final Map<String, Class<?>> installedClasses = new HashMap<>();
|
||||
final String mainClassName = script.getMainClassName();
|
||||
final byte[] mainClassBytes = script.getClassBytes().get(mainClassName);
|
||||
|
||||
final Class<?> mainClass = installer.install(mainClassName, mainClassBytes);
|
||||
|
||||
installedClasses.put(mainClassName, mainClass);
|
||||
|
||||
for (final Map.Entry<String, byte[]> entry : script.getClassBytes().entrySet()) {
|
||||
final String className = entry.getKey();
|
||||
final byte[] code = entry.getValue();
|
||||
|
||||
if (className.equals(mainClassName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
installedClasses.put(className, installer.install(className, code));
|
||||
}
|
||||
|
||||
final Map<Integer, FunctionInitializer> initializers = script.getInitializers();
|
||||
assert initializers != null;
|
||||
assert initializers.size() == 1;
|
||||
final FunctionInitializer initializer = initializers.values().iterator().next();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
installer.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;
|
||||
}
|
||||
|
||||
private MethodType explicitParams(final MethodType callSiteType) {
|
||||
@ -502,61 +552,57 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
return f;
|
||||
}
|
||||
|
||||
MethodHandle lookup(final FunctionInitializer fnInit) {
|
||||
final MethodType type = fnInit.getMethodType();
|
||||
return lookupCodeMethod(fnInit.getCode(), type);
|
||||
}
|
||||
|
||||
MethodHandle lookup(final FunctionNode fn) {
|
||||
final MethodType type = new FunctionSignature(fn).getMethodType();
|
||||
log.info("Looking up ", DebugLogger.quote(fn.getName()), " type=", type);
|
||||
return lookupWithExplicitType(fn, new FunctionSignature(fn).getMethodType());
|
||||
return lookupCodeMethod(fn.getCompileUnit().getCode(), type);
|
||||
}
|
||||
|
||||
MethodHandle lookupWithExplicitType(final FunctionNode fn, final MethodType targetType) {
|
||||
return lookupCodeMethod(fn.getCompileUnit(), targetType);
|
||||
}
|
||||
|
||||
private MethodHandle lookupCodeMethod(final CompileUnit compileUnit, final MethodType targetType) {
|
||||
return MH.findStatic(LOOKUP, compileUnit.getCode(), functionName, targetType);
|
||||
MethodHandle lookupCodeMethod(final Class<?> code, final MethodType targetType) {
|
||||
log.info("Looking up ", DebugLogger.quote(name), " type=", targetType);
|
||||
return MH.findStatic(LOOKUP, code, functionName, targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this function data with the eagerly generated version of the code. This method can only be invoked
|
||||
* by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it
|
||||
* externally will result in an exception.
|
||||
* @param functionNode the functionNode belonging to this data
|
||||
*/
|
||||
public void initializeCode(final FunctionNode functionNode) {
|
||||
public void initializeCode(final FunctionInitializer initializer) {
|
||||
// Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit.
|
||||
if(!(code.isEmpty() && functionNode.getCompileUnit().isInitializing(this, functionNode))) {
|
||||
throw new IllegalStateException(functionNode.getName() + " id=" + functionNode.getId());
|
||||
if(!code.isEmpty()) {
|
||||
throw new IllegalStateException(name);
|
||||
}
|
||||
addCode(functionNode);
|
||||
addCode(lookup(initializer), null, null, initializer.getFlags());
|
||||
}
|
||||
|
||||
private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints, final int fnFlags) {
|
||||
final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, fnFlags);
|
||||
private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints,
|
||||
final MethodType callSiteType, final int fnFlags) {
|
||||
final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags);
|
||||
code.add(cfn);
|
||||
return cfn;
|
||||
}
|
||||
|
||||
private CompiledFunction addCode(final FunctionNode fn) {
|
||||
return addCode(lookup(fn), null, fn.getFlags());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code with specific call site type. It will adapt the type of the looked up method handle to fit the call site
|
||||
* type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end
|
||||
* up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of
|
||||
* a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups
|
||||
* for the same specialization, so we must adapt the handle to the expected type.
|
||||
* @param tfn the function
|
||||
* @param fnInit the function
|
||||
* @param callSiteType the call site type
|
||||
* @return the compiled function object, with its type matching that of the call site type.
|
||||
*/
|
||||
private CompiledFunction addCode(final TypeSpecializedFunction tfn, final MethodType callSiteType) {
|
||||
final FunctionNode fn = tfn.fn;
|
||||
if (fn.isVarArg()) {
|
||||
return addCode(fn);
|
||||
private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) {
|
||||
if (isVariableArity()) {
|
||||
return addCode(lookup(fnInit), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
|
||||
}
|
||||
|
||||
final MethodHandle handle = lookup(fn);
|
||||
final MethodHandle handle = lookup(fnInit);
|
||||
final MethodType fromType = handle.type();
|
||||
MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1);
|
||||
toType = toType.changeReturnType(fromType.returnType());
|
||||
@ -581,41 +627,39 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
toType = toType.dropParameterTypes(fromCount, toCount);
|
||||
}
|
||||
|
||||
return addCode(lookup(fn).asType(toType), tfn.invalidatedProgramPoints, fn.getFlags());
|
||||
return addCode(lookup(fnInit).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope) {
|
||||
synchronized (code) {
|
||||
CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope);
|
||||
if (existingBest == null) {
|
||||
existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType);
|
||||
}
|
||||
|
||||
assert existingBest != null;
|
||||
//we are calling a vararg method with real args
|
||||
boolean applyToCall = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType);
|
||||
|
||||
//if the best one is an apply to call, it has to match the callsite exactly
|
||||
//or we need to regenerate
|
||||
if (existingBest.isApplyToCall()) {
|
||||
final CompiledFunction best = code.lookupExactApplyToCall(callSiteType);
|
||||
if (best != null) {
|
||||
return best;
|
||||
}
|
||||
applyToCall = true;
|
||||
}
|
||||
|
||||
if (applyToCall) {
|
||||
final TypeSpecializedFunction tfn = compileTypeSpecialization(callSiteType, runtimeScope);
|
||||
if (tfn.fn.hasOptimisticApplyToCall()) { //did the specialization work
|
||||
existingBest = addCode(tfn, callSiteType);
|
||||
}
|
||||
}
|
||||
|
||||
return existingBest;
|
||||
synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope) {
|
||||
CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope);
|
||||
if (existingBest == null) {
|
||||
existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType);
|
||||
}
|
||||
|
||||
assert existingBest != null;
|
||||
//we are calling a vararg method with real args
|
||||
boolean applyToCall = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType);
|
||||
|
||||
//if the best one is an apply to call, it has to match the callsite exactly
|
||||
//or we need to regenerate
|
||||
if (existingBest.isApplyToCall()) {
|
||||
final CompiledFunction best = lookupExactApplyToCall(callSiteType);
|
||||
if (best != null) {
|
||||
return best;
|
||||
}
|
||||
applyToCall = true;
|
||||
}
|
||||
|
||||
if (applyToCall) {
|
||||
final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false);
|
||||
if ((fnInit.getFlags() & FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION) != 0) { //did the specialization work
|
||||
existingBest = addCode(fnInit, callSiteType);
|
||||
}
|
||||
}
|
||||
|
||||
return existingBest;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -637,6 +681,18 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
return MethodType.genericMethodType(2 + getArity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the function node id.
|
||||
* @return the function node id
|
||||
*/
|
||||
public int getFunctionNodeId() {
|
||||
return functionNodeId;
|
||||
}
|
||||
|
||||
public Source getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a script function data based on a function id, either this function if
|
||||
* the id matches or a nested function based on functionId. This goes down into
|
||||
|
||||
@ -35,6 +35,9 @@ import java.io.Serializable;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
|
||||
|
||||
|
||||
@ -54,9 +57,10 @@ public abstract class ScriptFunctionData implements Serializable {
|
||||
/** Name of the function or "" for anonymous functions */
|
||||
protected final String name;
|
||||
|
||||
/** All versions of this function that have been generated to code */
|
||||
// TODO: integrate it into ScriptFunctionData; there's not much reason for this to be in its own class.
|
||||
protected transient CompiledFunctions code;
|
||||
/**
|
||||
* A list of code versions of a function sorted in ascending order of generic descriptors.
|
||||
*/
|
||||
protected transient LinkedList<CompiledFunction> code = new LinkedList<>();
|
||||
|
||||
/** Function flags */
|
||||
protected int flags;
|
||||
@ -71,7 +75,7 @@ public abstract class ScriptFunctionData implements Serializable {
|
||||
* multiple threads concurrently, but we still tolerate a race condition in it as all values stored into it are
|
||||
* idempotent.
|
||||
*/
|
||||
private volatile GenericInvokers genericInvokers;
|
||||
private volatile transient GenericInvokers genericInvokers;
|
||||
|
||||
private static final MethodHandle BIND_VAR_ARGS = findOwnMH("bindVarArgs", Object[].class, Object[].class, Object[].class);
|
||||
|
||||
@ -108,7 +112,6 @@ public abstract class ScriptFunctionData implements Serializable {
|
||||
*/
|
||||
ScriptFunctionData(final String name, final int arity, final int flags) {
|
||||
this.name = name;
|
||||
this.code = new CompiledFunctions(name);
|
||||
this.flags = flags;
|
||||
setArity(arity);
|
||||
}
|
||||
@ -297,6 +300,50 @@ public abstract class ScriptFunctionData implements Serializable {
|
||||
return lgenericInvokers;
|
||||
}
|
||||
|
||||
private static MethodType widen(final MethodType cftype) {
|
||||
final Class<?>[] paramTypes = new Class<?>[cftype.parameterCount()];
|
||||
for (int i = 0; i < cftype.parameterCount(); i++) {
|
||||
paramTypes[i] = cftype.parameterType(i).isPrimitive() ? cftype.parameterType(i) : Object.class;
|
||||
}
|
||||
return MH.type(cftype.returnType(), paramTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to find an apply to call version that fits this callsite.
|
||||
* We cannot just, as in the normal matcher case, return e.g. (Object, Object, int)
|
||||
* for (Object, Object, int, int, int) or we will destroy the semantics and get
|
||||
* a function that, when padded with undefineds, behaves differently
|
||||
* @param type actual call site type
|
||||
* @return apply to call that perfectly fits this callsite or null if none found
|
||||
*/
|
||||
CompiledFunction lookupExactApplyToCall(final MethodType type) {
|
||||
for (final CompiledFunction cf : code) {
|
||||
if (!cf.isApplyToCall()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final MethodType cftype = cf.type();
|
||||
if (cftype.parameterCount() != type.parameterCount()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (widen(cftype).equals(widen(type))) {
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
CompiledFunction pickFunction(final MethodType callSiteType, final boolean canPickVarArg) {
|
||||
for (final CompiledFunction candidate : code) {
|
||||
if (candidate.matchesCallSite(callSiteType, canPickVarArg)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best function for the specified call site type.
|
||||
* @param callSiteType The call site type. Call site types are expected to have the form
|
||||
@ -307,16 +354,38 @@ public abstract class ScriptFunctionData implements Serializable {
|
||||
* @return the best function for the specified call site type.
|
||||
*/
|
||||
CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope) {
|
||||
return code.best(callSiteType, isRecompilable());
|
||||
assert callSiteType.parameterCount() >= 2 : callSiteType; // Must have at least (callee, this)
|
||||
assert callSiteType.parameterType(0).isAssignableFrom(ScriptFunction.class) : callSiteType; // Callee must be assignable from script function
|
||||
|
||||
if (isRecompilable()) {
|
||||
final CompiledFunction candidate = pickFunction(callSiteType, false);
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
return pickFunction(callSiteType, true); //try vararg last
|
||||
}
|
||||
|
||||
CompiledFunction best = null;
|
||||
for(final CompiledFunction candidate: code) {
|
||||
if(candidate.betterThanFinal(best, callSiteType)) {
|
||||
best = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
|
||||
abstract boolean isRecompilable();
|
||||
|
||||
CompiledFunction getGeneric(final ScriptObject runtimeScope) {
|
||||
return getBest(getGenericType(), runtimeScope);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a method type for a generic invoker.
|
||||
* @return the method type for the generic invoker
|
||||
*/
|
||||
abstract MethodType getGenericType();
|
||||
|
||||
/**
|
||||
@ -352,7 +421,7 @@ public abstract class ScriptFunctionData implements Serializable {
|
||||
// Clear the callee and this flags
|
||||
final int boundFlags = flags & ~NEEDS_CALLEE & ~USES_THIS;
|
||||
|
||||
final CompiledFunctions boundList = new CompiledFunctions(fn.getName());
|
||||
final List<CompiledFunction> boundList = new LinkedList<>();
|
||||
final ScriptObject runtimeScope = fn.getScope();
|
||||
final CompiledFunction bindTarget = new CompiledFunction(getGenericInvoker(runtimeScope), getGenericConstructor(runtimeScope));
|
||||
boundList.add(bind(bindTarget, fn, self, allArgs));
|
||||
@ -805,6 +874,6 @@ public abstract class ScriptFunctionData implements Serializable {
|
||||
|
||||
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
code = new CompiledFunctions(name);
|
||||
code = new LinkedList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +87,9 @@ public final class Source implements Loggable {
|
||||
/** Base64-encoded SHA1 digest of this source object */
|
||||
private volatile byte[] digest;
|
||||
|
||||
/** source URL set via //@ sourceURL or //# sourceURL directive */
|
||||
private String explicitURL;
|
||||
|
||||
// Do *not* make this public, ever! Trusts the URL and content.
|
||||
private Source(final String name, final String base, final Data data) {
|
||||
this.name = name;
|
||||
@ -596,6 +599,22 @@ public final class Source implements Loggable {
|
||||
return data.url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get explicit source URL.
|
||||
* @return URL set vial sourceURL directive
|
||||
*/
|
||||
public String getExplicitURL() {
|
||||
return explicitURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set explicit source URL.
|
||||
* @param explicitURL URL set via sourceURL directive
|
||||
*/
|
||||
public void setExplicitURL(String explicitURL) {
|
||||
this.explicitURL = explicitURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this source was submitted via 'eval' call or not.
|
||||
*
|
||||
|
||||
@ -211,4 +211,12 @@ public class SpillProperty extends AccessorProperty {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
void initMethodHandles(Class<?> structure) {
|
||||
final int slot = getSlot();
|
||||
primitiveGetter = primitiveGetter(slot);
|
||||
primitiveSetter = primitiveSetter(slot);
|
||||
objectGetter = objectGetter(slot);
|
||||
objectSetter = objectSetter(slot);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,9 +30,12 @@ import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class representing a compiled script.
|
||||
* Class representing a persistent compiled script.
|
||||
*/
|
||||
final class CompiledScript implements Serializable {
|
||||
public final class StoredScript implements Serializable {
|
||||
|
||||
/** Compilation id */
|
||||
private final int compilationId;
|
||||
|
||||
/** Main class name. */
|
||||
private final String mainClassName;
|
||||
@ -43,8 +46,8 @@ final class CompiledScript implements Serializable {
|
||||
/** Constants array. */
|
||||
private final Object[] constants;
|
||||
|
||||
/** The source */
|
||||
private transient Source source;
|
||||
/** Function initializers */
|
||||
private final Map<Integer, FunctionInitializer> initializers;
|
||||
|
||||
private static final long serialVersionUID = 2958227232195298340L;
|
||||
|
||||
@ -55,11 +58,16 @@ final class CompiledScript implements Serializable {
|
||||
* @param classBytes map of class names to class bytes
|
||||
* @param constants constants array
|
||||
*/
|
||||
CompiledScript(final Source source, final String mainClassName, final Map<String, byte[]> classBytes, final Object[] constants) {
|
||||
this.source = source;
|
||||
public StoredScript(final int compilationId, final String mainClassName, final Map<String, byte[]> classBytes, final Map<Integer, FunctionInitializer> initializers, final Object[] constants) {
|
||||
this.compilationId = compilationId;
|
||||
this.mainClassName = mainClassName;
|
||||
this.classBytes = classBytes;
|
||||
this.constants = constants;
|
||||
this.initializers = initializers;
|
||||
}
|
||||
|
||||
public int getCompilationId() {
|
||||
return compilationId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,20 +94,8 @@ final class CompiledScript implements Serializable {
|
||||
return constants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source of this cached script.
|
||||
* @return the source
|
||||
*/
|
||||
public Source getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source of this cached script.
|
||||
* @param source the source
|
||||
*/
|
||||
void setSource(final Source source) {
|
||||
this.source = source;
|
||||
Map<Integer, FunctionInitializer> getInitializers() {
|
||||
return initializers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -115,11 +111,11 @@ final class CompiledScript implements Serializable {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof CompiledScript)) {
|
||||
if (!(obj instanceof StoredScript)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final CompiledScript cs = (CompiledScript) obj;
|
||||
final StoredScript cs = (StoredScript) obj;
|
||||
return mainClassName.equals(cs.mainClassName)
|
||||
&& classBytes.equals(cs.classBytes)
|
||||
&& Arrays.equals(constants, cs.constants);
|
||||
@ -266,7 +266,6 @@ public class Shell {
|
||||
env,
|
||||
null,
|
||||
functionNode.getSource(),
|
||||
functionNode.getSourceURL(),
|
||||
env._strict | functionNode.isStrict()).
|
||||
compile(functionNode, CompilationPhases.COMPILE_ALL_NO_INSTALL);
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ var getEnvMethod = Context.class.getMethod("getEnv")
|
||||
|
||||
var sourceForMethod = Source.class.getMethod("sourceFor", java.lang.String.class, java.lang.String.class)
|
||||
var ParserConstructor = Parser.class.getConstructor(ScriptEnvironment.class, Source.class, ErrorManager.class)
|
||||
var CompilerConstructor = Compiler.class.getConstructor(Context.class, ScriptEnvironment.class, CodeInstaller.class, Source.class, String.class, boolean.class);
|
||||
var CompilerConstructor = Compiler.class.getConstructor(Context.class, ScriptEnvironment.class, CodeInstaller.class, Source.class, boolean.class);
|
||||
|
||||
// compile(script) -- compiles a script specified as a string with its
|
||||
// source code, returns a jdk.nashorn.internal.ir.FunctionNode object
|
||||
@ -134,7 +134,7 @@ function compile(source, phases) {
|
||||
var parser = ParserConstructor.newInstance(env, source, ThrowErrorManager.class.newInstance());
|
||||
var func = parseMethod.invoke(parser);
|
||||
|
||||
var compiler = CompilerConstructor.newInstance(ctxt, env, null, source, null, false);
|
||||
var compiler = CompilerConstructor.newInstance(ctxt, env, null, source, false);
|
||||
|
||||
return compileMethod.invoke(compiler, func, phases);
|
||||
};
|
||||
|
||||
@ -96,7 +96,7 @@ public class CodeStoreAndPathTest {
|
||||
final String codeCache = "build/nashorn_code_cache";
|
||||
final String oldUserDir = System.getProperty("user.dir");
|
||||
|
||||
private static final String[] ENGINE_OPTIONS = new String[]{"--persistent-code-cache"};
|
||||
private static final String[] ENGINE_OPTIONS = new String[]{"--persistent-code-cache", "--optimistic-types=false", "--lazy-compilation=false"};
|
||||
|
||||
public void checkCompiledScripts(final DirectoryStream<Path> stream, int numberOfScripts) throws IOException {
|
||||
for (final Path file : stream) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user