aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/CompiledFunction.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/nashorn/internal/runtime/CompiledFunction.java')
-rw-r--r--src/jdk/nashorn/internal/runtime/CompiledFunction.java134
1 files changed, 110 insertions, 24 deletions
diff --git a/src/jdk/nashorn/internal/runtime/CompiledFunction.java b/src/jdk/nashorn/internal/runtime/CompiledFunction.java
index 28a81dea..b37f94fa 100644
--- a/src/jdk/nashorn/internal/runtime/CompiledFunction.java
+++ b/src/jdk/nashorn/internal/runtime/CompiledFunction.java
@@ -27,18 +27,20 @@ package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
+
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.invoke.SwitchPoint;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.logging.Level;
-
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
@@ -46,6 +48,7 @@ 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;
+import jdk.nashorn.internal.objects.annotations.SpecializedFunction.LinkLogic;
import jdk.nashorn.internal.runtime.events.RecompilationEvent;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
@@ -63,6 +66,8 @@ final class CompiledFunction {
private final DebugLogger log;
+ static final Collection<CompiledFunction> NO_FUNCTIONS = Collections.emptySet();
+
/**
* The method type may be more specific than the invoker, if. e.g.
* the invoker is guarded, and a guard with a generic object only
@@ -75,19 +80,38 @@ final class CompiledFunction {
private final int flags; // from FunctionNode
private final MethodType callSiteType;
+ private final Specialization specialization;
+
CompiledFunction(final MethodHandle invoker) {
- this(invoker, null);
+ this(invoker, null, null);
}
- static CompiledFunction createBuiltInConstructor(final MethodHandle invoker) {
- return new CompiledFunction(MH.insertArguments(invoker, 0, false), createConstructorFromInvoker(MH.insertArguments(invoker, 0, true)));
+ static CompiledFunction createBuiltInConstructor(final MethodHandle invoker, final Specialization specialization) {
+ return new CompiledFunction(MH.insertArguments(invoker, 0, false), createConstructorFromInvoker(MH.insertArguments(invoker, 0, true)), specialization);
}
- CompiledFunction(final MethodHandle invoker, final MethodHandle constructor) {
- this(invoker, constructor, 0, null, DebugLogger.DISABLED_LOGGER);
+ CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final Specialization specialization) {
+ this(invoker, constructor, 0, null, specialization, DebugLogger.DISABLED_LOGGER);
}
- CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final int flags, final MethodType callSiteType, final DebugLogger log) {
+ CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final int flags, final MethodType callSiteType, final Specialization specialization, final DebugLogger log) {
+ this.specialization = specialization;
+ if (specialization != null && specialization.isOptimistic()) {
+ /*
+ * An optimistic builtin with isOptimistic=true works like any optimistic generated function, i.e. it
+ * can throw unwarranted optimism exceptions. As native functions trivially can't have parts of them
+ * regenerated as restof methods, this only works if the methods are atomic/functional in their behavior
+ * and doesn't modify state before an UOE can be thrown. If they aren't, we can reexecute a wider version
+ * of the same builtin in a recompilation handler for FinalScriptFunctionData. There are several
+ * candidate methods in Native* that would benefit from this, but I haven't had time to implement any
+ * of them currently. In order to fit in with the relinking framework, the current thinking is
+ * that the methods still take a program point to fit in with other optimistic functions, but
+ * it is set to "first", which is the beginning of the method. The relinker can tell the difference
+ * between builtin and JavaScript functions. This might change. TODO
+ */
+ this.invoker = MH.insertArguments(invoker, invoker.type().parameterCount() - 1, UnwarrantedOptimismException.FIRST_PROGRAM_POINT);
+ throw new AssertionError("Optimistic (UnwarrantedOptimismException throwing) builtin functions are currently not in use");
+ }
this.invoker = invoker;
this.constructor = constructor;
this.flags = flags;
@@ -97,7 +121,7 @@ final class CompiledFunction {
CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData,
final Map<Integer, Type> invalidatedProgramPoints, final MethodType callSiteType, final int flags) {
- this(invoker, null, flags, callSiteType, functionData.getLogger());
+ this(invoker, null, flags, callSiteType, null, functionData.getLogger());
if ((flags & FunctionNode.IS_DEOPTIMIZABLE) != 0) {
optimismInfo = new OptimismInfo(functionData, invalidatedProgramPoints);
} else {
@@ -105,10 +129,45 @@ final class CompiledFunction {
}
}
+ static CompiledFunction createBuiltInConstructor(final MethodHandle invoker) {
+ return new CompiledFunction(MH.insertArguments(invoker, 0, false), createConstructorFromInvoker(MH.insertArguments(invoker, 0, true)), null);
+ }
+
+ boolean isSpecialization() {
+ return specialization != null;
+ }
+
+ boolean hasLinkLogic() {
+ return getLinkLogicClass() != null;
+ }
+
+ Class<? extends LinkLogic> getLinkLogicClass() {
+ if (isSpecialization()) {
+ final Class<? extends LinkLogic> linkLogicClass = specialization.getLinkLogicClass();
+ assert !LinkLogic.isEmpty(linkLogicClass) : "empty link logic classes should have been removed by nasgen";
+ return linkLogicClass;
+ }
+ return null;
+ }
+
int getFlags() {
return flags;
}
+ /**
+ * An optimistic specialization is one that can throw UnwarrantedOptimismException.
+ * This is allowed for native methods, as long as they are functional, i.e. don't change
+ * any state between entering and throwing the UOE. Then we can re-execute a wider version
+ * of the method in the continuation. Rest-of method generation for optimistic builtins is
+ * of course not possible, but this approach works and fits into the same relinking
+ * framework
+ *
+ * @return true if optimistic builtin
+ */
+ boolean isOptimistic() {
+ return isSpecialization() ? specialization.isOptimistic() : false;
+ }
+
boolean isApplyToCall() {
return (flags & FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION) != 0;
}
@@ -119,7 +178,19 @@ final class CompiledFunction {
@Override
public String toString() {
- return "[invokerType=" + invoker.type() + " ctor=" + constructor + " weight=" + weight() + " isApplyToCall=" + isApplyToCall() + "]";
+ final StringBuilder sb = new StringBuilder();
+ final Class<? extends LinkLogic> linkLogicClass = getLinkLogicClass();
+
+ sb.append("[invokerType=").
+ append(invoker.type()).
+ append(" ctor=").
+ append(constructor).
+ append(" weight=").
+ append(weight()).
+ append(" linkLogic=").
+ append(linkLogicClass != null ? linkLogicClass.getSimpleName() : "none");
+
+ return sb.toString();
}
boolean needsCallee() {
@@ -281,10 +352,12 @@ final class CompiledFunction {
if (other == null) {
return true;
}
- return betterThanFinal(type(), other.type(), callSiteMethodType);
+ return betterThanFinal(this, other, callSiteMethodType);
}
- static boolean betterThanFinal(final MethodType thisMethodType, final MethodType otherMethodType, final MethodType callSiteMethodType) {
+ private static boolean betterThanFinal(final CompiledFunction cf, final CompiledFunction other, final MethodType callSiteMethodType) {
+ final MethodType thisMethodType = cf.type();
+ final MethodType otherMethodType = other.type();
final int thisParamCount = getParamCount(thisMethodType);
final int otherParamCount = getParamCount(otherMethodType);
final int callSiteRawParamCount = getParamCount(callSiteMethodType);
@@ -406,7 +479,17 @@ final class CompiledFunction {
return false;
}
- throw new AssertionError(thisMethodType + " identically applicable to " + otherMethodType + " for " + callSiteMethodType); // Signatures are identical
+ //if they are equal, pick the specialized one first
+ if (cf.isSpecialization() != other.isSpecialization()) {
+ return cf.isSpecialization(); //always pick the specialized version if we can
+ }
+
+ if (cf.isSpecialization() && other.isSpecialization()) {
+ return cf.getLinkLogicClass() != null; //pick link logic specialization above generic specializations
+ }
+
+ // Signatures are identical
+ throw new AssertionError(thisMethodType + " identically applicable to " + otherMethodType + " for " + callSiteMethodType);
}
private static Type[] toTypeWithoutCallee(final MethodType type, final int thisIndex) {
@@ -427,8 +510,8 @@ final class CompiledFunction {
return ((ArrayType)paramTypes[paramTypes.length - 1]).getElementType();
}
- boolean matchesCallSite(final MethodType callSiteType, final boolean pickVarArg) {
- if (callSiteType.equals(this.callSiteType)) {
+ boolean matchesCallSite(final MethodType other, final boolean pickVarArg) {
+ if (other.equals(this.callSiteType)) {
return true;
}
final MethodType type = type();
@@ -438,7 +521,7 @@ final class CompiledFunction {
return pickVarArg;
}
- final int csParamCount = getParamCount(callSiteType);
+ final int csParamCount = getParamCount(other);
final boolean csIsVarArg = csParamCount == Integer.MAX_VALUE;
final int thisThisIndex = needsCallee() ? 1 : 0; // Index of "this" parameter in this function's type
@@ -447,7 +530,7 @@ final class CompiledFunction {
// We must match all incoming parameters, except "this". Starting from 1 to skip "this".
for(int i = 1; i < minParams; ++i) {
final Type fnType = Type.typeFor(type.parameterType(i + thisThisIndex));
- final Type csType = csIsVarArg ? Type.OBJECT : Type.typeFor(callSiteType.parameterType(i + 1));
+ final Type csType = csIsVarArg ? Type.OBJECT : Type.typeFor(other.parameterType(i + 1));
if(!fnType.isEquivalentTo(csType)) {
return false;
}
@@ -669,9 +752,9 @@ final class CompiledFunction {
return sb.toString();
}
- private void logRecompile(final String reason, final FunctionNode fn, final MethodType callSiteType, final Map<Integer, Type> ipp) {
+ private void logRecompile(final String reason, final FunctionNode fn, final MethodType type, final Map<Integer, Type> ipp) {
if (log.isEnabled()) {
- log.info(reason, DebugLogger.quote(fn.getName()), " signature: ", callSiteType, " ", toStringInvalidations(ipp));
+ log.info(reason, DebugLogger.quote(fn.getName()), " signature: ", type, " ", toStringInvalidations(ipp));
}
}
@@ -704,6 +787,7 @@ final class CompiledFunction {
// isn't available, we'll use the old one bound into the call site.
final OptimismInfo effectiveOptInfo = currentOptInfo != null ? currentOptInfo : oldOptInfo;
FunctionNode fn = effectiveOptInfo.reparse();
+ final boolean serialized = effectiveOptInfo.isSerialized();
final Compiler compiler = effectiveOptInfo.getCompiler(fn, callSiteType, re); //set to non rest-of
if (!shouldRecompile) {
@@ -711,17 +795,17 @@ final class CompiledFunction {
// recompiled a deoptimized version for an inner invocation.
// We still need to do the rest of from the beginning
logRecompile("Rest-of compilation [STANDALONE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
- return restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
+ return restOfHandle(effectiveOptInfo, compiler.compile(fn, serialized ? CompilationPhases.COMPILE_SERIALIZED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
}
logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
- fn = compiler.compile(fn, CompilationPhases.COMPILE_UPTO_BYTECODE);
+ fn = compiler.compile(fn, serialized ? CompilationPhases.RECOMPILE_SERIALIZED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE);
log.info("Reusable IR generated");
// compile the rest of the function, and install it
log.info("Generating and installing bytecode from reusable IR...");
logRecompile("Rest-of compilation [CODE PIPELINE REUSE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
- final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.COMPILE_FROM_BYTECODE);
+ final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL);
if (effectiveOptInfo.data.usePersistentCodeCache()) {
final RecompilableScriptFunctionData data = effectiveOptInfo.data;
@@ -732,8 +816,6 @@ final class CompiledFunction {
compiler.persistClassInfo(cacheKey, normalFn);
}
- FunctionNode fn2 = effectiveOptInfo.reparse();
- fn2 = compiler.compile(fn2, CompilationPhases.COMPILE_UPTO_BYTECODE);
log.info("Done.");
final boolean canBeDeoptimized = normalFn.canBeDeoptimized();
@@ -749,7 +831,7 @@ final class CompiledFunction {
constructor = null; // Will be regenerated when needed
log.info("Done: ", invoker);
- final MethodHandle restOf = restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.COMPILE_FROM_BYTECODE_RESTOF), canBeDeoptimized);
+ final MethodHandle restOf = restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL_RESTOF), canBeDeoptimized);
// Note that we only adjust the switch point after we set the invoker/constructor. This is important.
if (canBeDeoptimized) {
@@ -841,6 +923,10 @@ final class CompiledFunction {
FunctionNode reparse() {
return data.reparse();
}
+
+ boolean isSerialized() {
+ return data.isSerialized();
+ }
}
@SuppressWarnings("unused")