/*
* 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 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.logging.Level;
import jdk.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;
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;
An version of a JavaScript function, native or JavaScript.
Supports lazily generating a constructor version of the invocation.
/**
* An version of a JavaScript function, native or JavaScript.
* Supports lazily generating a constructor version of the invocation.
*/
final class CompiledFunction {
private static final MethodHandle NEWFILTER = findOwnMH("newFilter", Object.class, Object.class, Object.class);
private static final MethodHandle RELINK_COMPOSABLE_INVOKER = findOwnMH("relinkComposableInvoker", void.class, CallSite.class, CompiledFunction.class, boolean.class);
private static final MethodHandle HANDLE_REWRITE_EXCEPTION = findOwnMH("handleRewriteException", MethodHandle.class, CompiledFunction.class, OptimismInfo.class, RewriteException.class);
private static final MethodHandle RESTOF_INVOKER = MethodHandles.exactInvoker(MethodType.methodType(Object.class, RewriteException.class));
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
fallback, while the target is more specific, we still need the
more specific type for sorting
/**
* 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
* fallback, while the target is more specific, we still need the
* more specific type for sorting
*/
private MethodHandle invoker;
private MethodHandle constructor;
private OptimismInfo optimismInfo;
private final int flags; // from FunctionNode
private final MethodType callSiteType;
private final Specialization specialization;
CompiledFunction(final MethodHandle invoker) {
this(invoker, null, null);
}
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, 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 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;
this.callSiteType = callSiteType;
this.log = log;
}
CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData,
final Map<Integer, Type> invalidatedProgramPoints, final MethodType callSiteType, final int flags) {
this(invoker, null, flags, callSiteType, null, functionData.getLogger());
if ((flags & FunctionNode.IS_DEOPTIMIZABLE) != 0) {
optimismInfo = new OptimismInfo(functionData, invalidatedProgramPoints);
} else {
optimismInfo = null;
}
}
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;
}
boolean convertsNumericArgs() {
return isSpecialization() && specialization.convertsNumericArgs();
}
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
Returns: true if optimistic builtin
/**
* 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;
}
boolean isVarArg() {
return isVarArgsType(invoker.type());
}
@Override
public String toString() {
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() {
return ScriptFunctionData.needsCallee(invoker);
}
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 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. Returns: a guaranteed composable invoker method handle for this function.
/**
* 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 #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() {
return createComposableInvoker(false);
}
Returns an invoker method handle for this function when invoked as a constructor. Note that the handle should be considered non-composable in the sense that you can only compose it with other handles using any combinators if you can ensure that the composition is guarded by getOptimisticAssumptionsSwitchPoint()
if it's non-null, and that you can relink the call site it is set into as a target if the switch point is invalidated. In all other cases, use createComposableConstructor()
. Returns: a direct constructor method handle for this function.
/**
* Returns an invoker method handle for this function when invoked as a constructor. Note that the handle should be
* considered non-composable in the sense that you can only compose it with other handles using any combinators if
* you can ensure that the composition is guarded by {@link #getOptimisticAssumptionsSwitchPoint()} if it's
* non-null, and that you can relink the call site it is set into as a target if the switch point is invalidated. In
* all other cases, use {@link #createComposableConstructor()}.
* @return a direct constructor method handle for this function.
*/
private MethodHandle getConstructor() {
if (constructor == null) {
constructor = createConstructorFromInvoker(createInvokerForPessimisticCaller());
}
return constructor;
}
Creates a version of the invoker intended for a pessimistic caller (return type is Object, no caller optimistic
program point available).
Returns: a version of the invoker intended for a pessimistic caller.
/**
* Creates a version of the invoker intended for a pessimistic caller (return type is Object, no caller optimistic
* program point available).
* @return a version of the invoker intended for a pessimistic caller.
*/
private MethodHandle createInvokerForPessimisticCaller() {
return createInvoker(Object.class, INVALID_PROGRAM_POINT);
}
Compose a constructor from an invoker.
Params: - invoker – invoker
Returns: the composed constructor
/**
* Compose a constructor from an invoker.
*
* @param invoker invoker
* @return the composed constructor
*/
private static MethodHandle createConstructorFromInvoker(final MethodHandle invoker) {
final boolean needsCallee = ScriptFunctionData.needsCallee(invoker);
// If it was (callee, this, args...), permute it to (this, callee, args...). We're doing this because having
// "this" in the first argument position is what allows the elegant folded composition of
// (newFilter x constructor x allocator) further down below in the code. Also, ensure the composite constructor
// always returns Object.
final MethodHandle swapped = needsCallee ? swapCalleeAndThis(invoker) : invoker;
final MethodHandle returnsObject = MH.asType(swapped, swapped.type().changeReturnType(Object.class));
final MethodType ctorType = returnsObject.type();
// Construct a dropping type list for NEWFILTER, but don't include constructor "this" into it, so it's actually
// captured as "allocation" parameter of NEWFILTER after we fold the constructor into it.
// (this, [callee, ]args...) => ([callee, ]args...)
final Class<?>[] ctorArgs = ctorType.dropParameterTypes(0, 1).parameterArray();
// Fold constructor into newFilter that replaces the return value from the constructor with the originally
// allocated value when the originally allocated value is a JS primitive (String, Boolean, Number).
// (result, this, [callee, ]args...) x (this, [callee, ]args...) => (this, [callee, ]args...)
final MethodHandle filtered = MH.foldArguments(MH.dropArguments(NEWFILTER, 2, ctorArgs), returnsObject);
// allocate() takes a ScriptFunction and returns a newly allocated ScriptObject...
if (needsCallee) {
// ...we either fold it into the previous composition, if we need both the ScriptFunction callee object and
// the newly allocated object in the arguments, so (this, callee, args...) x (callee) => (callee, args...),
// or...
return MH.foldArguments(filtered, ScriptFunction.ALLOCATE);
}
// ...replace the ScriptFunction argument with the newly allocated object, if it doesn't need the callee
// (this, args...) filter (callee) => (callee, args...)
return MH.filterArguments(filtered, 0, ScriptFunction.ALLOCATE);
}
Permutes the parameters in the method handle from (callee, this, ...)
to (this, callee, ...)
. Used when creating a constructor handle. Params: - mh – a method handle with order of arguments
(callee, this, ...)
Returns: a method handle with order of arguments (this, callee, ...)
/**
* Permutes the parameters in the method handle from {@code (callee, this, ...)} to {@code (this, callee, ...)}.
* Used when creating a constructor handle.
* @param mh a method handle with order of arguments {@code (callee, this, ...)}
* @return a method handle with order of arguments {@code (this, callee, ...)}
*/
private static MethodHandle swapCalleeAndThis(final MethodHandle mh) {
final MethodType type = mh.type();
assert type.parameterType(0) == ScriptFunction.class : type;
assert type.parameterType(1) == Object.class : type;
final MethodType newType = type.changeParameterType(0, Object.class).changeParameterType(1, ScriptFunction.class);
final int[] reorder = new int[type.parameterCount()];
reorder[0] = 1;
assert reorder[1] == 0;
for (int i = 2; i < reorder.length; ++i) {
reorder[i] = i;
}
return MethodHandles.permuteArguments(mh, newType, reorder);
}
Returns an invoker method handle for this function when invoked as a constructor. 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 getConstructor()
. 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. Returns: a guaranteed composable constructor method handle for this function.
/**
* Returns an invoker method handle for this function when invoked as a constructor. 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 #getConstructor()}. 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 constructor method handle for this function.
*/
MethodHandle createComposableConstructor() {
return createComposableInvoker(true);
}
boolean hasConstructor() {
return constructor != null;
}
MethodType type() {
return invoker.type();
}
int weight() {
return weight(type());
}
private static int weight(final MethodType type) {
if (isVarArgsType(type)) {
return Integer.MAX_VALUE; //if there is a varargs it should be the heavist and last fallback
}
int weight = Type.typeFor(type.returnType()).getWeight();
for (int i = 0 ; i < type.parameterCount() ; i++) {
final Class<?> paramType = type.parameterType(i);
final int pweight = Type.typeFor(paramType).getWeight() * 2; //params are more important than call types as return values are always specialized
weight += pweight;
}
weight += type.parameterCount(); //more params outweigh few parameters
return weight;
}
static boolean isVarArgsType(final MethodType type) {
assert type.parameterCount() >= 1 : type;
return type.parameterType(type.parameterCount() - 1) == Object[].class;
}
static boolean moreGenericThan(final MethodType mt0, final MethodType mt1) {
return weight(mt0) > weight(mt1);
}
boolean betterThanFinal(final CompiledFunction other, final MethodType callSiteMethodType) {
// Prefer anything over nothing, as we can't compile new versions.
if (other == null) {
return true;
}
return betterThanFinal(this, other, 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);
final boolean csVarArg = callSiteRawParamCount == Integer.MAX_VALUE;
// Subtract 1 for callee for non-vararg call sites
final int callSiteParamCount = csVarArg ? callSiteRawParamCount : callSiteRawParamCount - 1;
// Prefer the function that discards less parameters
final int thisDiscardsParams = Math.max(callSiteParamCount - thisParamCount, 0);
final int otherDiscardsParams = Math.max(callSiteParamCount - otherParamCount, 0);
if(thisDiscardsParams < otherDiscardsParams) {
return true;
}
if(thisDiscardsParams > otherDiscardsParams) {
return false;
}
final boolean thisVarArg = thisParamCount == Integer.MAX_VALUE;
final boolean otherVarArg = otherParamCount == Integer.MAX_VALUE;
if(!(thisVarArg && otherVarArg && csVarArg)) {
// At least one of them isn't vararg
final Type[] thisType = toTypeWithoutCallee(thisMethodType, 0); // Never has callee
final Type[] otherType = toTypeWithoutCallee(otherMethodType, 0); // Never has callee
final Type[] callSiteType = toTypeWithoutCallee(callSiteMethodType, 1); // Always has callee
int narrowWeightDelta = 0;
int widenWeightDelta = 0;
final int minParamsCount = Math.min(Math.min(thisParamCount, otherParamCount), callSiteParamCount);
final boolean convertsNumericArgs = cf.convertsNumericArgs();
for(int i = 0; i < minParamsCount; ++i) {
final Type callSiteParamType = getParamType(i, callSiteType, csVarArg);
final Type thisParamType = getParamType(i, thisType, thisVarArg);
if (!convertsNumericArgs && callSiteParamType.isBoolean() && thisParamType.isNumeric()) {
// When an argument is converted to number by a function it is safe to "widen" booleans to numeric types.
// However, we must avoid this conversion for generic functions such as Array.prototype.push.
return false;
}
final int callSiteParamWeight = callSiteParamType.getWeight();
// Delta is negative for narrowing, positive for widening
final int thisParamWeightDelta = thisParamType.getWeight() - callSiteParamWeight;
final int otherParamWeightDelta = getParamType(i, otherType, otherVarArg).getWeight() - callSiteParamWeight;
// Only count absolute values of narrowings
narrowWeightDelta += Math.max(-thisParamWeightDelta, 0) - Math.max(-otherParamWeightDelta, 0);
// Only count absolute values of widenings
widenWeightDelta += Math.max(thisParamWeightDelta, 0) - Math.max(otherParamWeightDelta, 0);
}
// If both functions accept more arguments than what is passed at the call site, account for ability
// to receive Undefined un-narrowed in the remaining arguments.
if(!thisVarArg) {
for(int i = callSiteParamCount; i < thisParamCount; ++i) {
narrowWeightDelta += Math.max(Type.OBJECT.getWeight() - thisType[i].getWeight(), 0);
}
}
if(!otherVarArg) {
for(int i = callSiteParamCount; i < otherParamCount; ++i) {
narrowWeightDelta -= Math.max(Type.OBJECT.getWeight() - otherType[i].getWeight(), 0);
}
}
// Prefer function that narrows less
if(narrowWeightDelta < 0) {
return true;
}
if(narrowWeightDelta > 0) {
return false;
}
// Prefer function that widens less
if(widenWeightDelta < 0) {
return true;
}
if(widenWeightDelta > 0) {
return false;
}
}
// Prefer the function that exactly matches the arity of the call site.
if(thisParamCount == callSiteParamCount && otherParamCount != callSiteParamCount) {
return true;
}
if(thisParamCount != callSiteParamCount && otherParamCount == callSiteParamCount) {
return false;
}
// Otherwise, neither function matches arity exactly. We also know that at this point, they both can receive
// more arguments than call site, otherwise we would've already chosen the one that discards less parameters.
// Note that variable arity methods are preferred, as they actually match the call site arity better, since they
// really have arbitrary arity.
if(thisVarArg) {
if(!otherVarArg) {
return true; //
}
} else if(otherVarArg) {
return false;
}
// Neither is variable arity; chose the one that has less extra parameters.
final int fnParamDelta = thisParamCount - otherParamCount;
if(fnParamDelta < 0) {
return true;
}
if(fnParamDelta > 0) {
return false;
}
final int callSiteRetWeight = Type.typeFor(callSiteMethodType.returnType()).getWeight();
// Delta is negative for narrower return type, positive for wider return type
final int thisRetWeightDelta = Type.typeFor(thisMethodType.returnType()).getWeight() - callSiteRetWeight;
final int otherRetWeightDelta = Type.typeFor(otherMethodType.returnType()).getWeight() - callSiteRetWeight;
// Prefer function that returns a less wide return type
final int widenRetDelta = Math.max(thisRetWeightDelta, 0) - Math.max(otherRetWeightDelta, 0);
if(widenRetDelta < 0) {
return true;
}
if(widenRetDelta > 0) {
return false;
}
// Prefer function that returns a less narrow return type
final int narrowRetDelta = Math.max(-thisRetWeightDelta, 0) - Math.max(-otherRetWeightDelta, 0);
if(narrowRetDelta < 0) {
return true;
}
if(narrowRetDelta > 0) {
return false;
}
//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) {
final int paramCount = type.parameterCount();
final Type[] t = new Type[paramCount - thisIndex];
for(int i = thisIndex; i < paramCount; ++i) {
t[i - thisIndex] = Type.typeFor(type.parameterType(i));
}
return t;
}
private static Type getParamType(final int i, final Type[] paramTypes, final boolean isVarArg) {
final int fixParamCount = paramTypes.length - (isVarArg ? 1 : 0);
if(i < fixParamCount) {
return paramTypes[i];
}
assert isVarArg;
return ((ArrayType)paramTypes[paramTypes.length - 1]).getElementType();
}
boolean matchesCallSite(final MethodType other, final boolean pickVarArg) {
if (other.equals(this.callSiteType)) {
return true;
}
final MethodType type = type();
final int fnParamCount = getParamCount(type);
final boolean isVarArg = fnParamCount == Integer.MAX_VALUE;
if (isVarArg) {
return pickVarArg;
}
final int csParamCount = getParamCount(other);
final boolean csIsVarArg = csParamCount == Integer.MAX_VALUE;
if (csIsVarArg && isApplyToCall()) {
return false; // apply2call function must be called with exact number of parameters
}
final int thisThisIndex = needsCallee() ? 1 : 0; // Index of "this" parameter in this function's type
final int fnParamCountNoCallee = fnParamCount - thisThisIndex;
final int minParams = Math.min(csParamCount - 1, fnParamCountNoCallee); // callSiteType always has callee, so subtract 1
// We must match all incoming parameters, including "this". "this" will usually be Object, but there
// are exceptions, e.g. when calling functions with primitive "this" in strict mode or through call/apply.
for(int i = 0; i < minParams; ++i) {
final Type fnType = Type.typeFor(type.parameterType(i + thisThisIndex));
final Type csType = csIsVarArg ? Type.OBJECT : Type.typeFor(other.parameterType(i + 1));
if(!fnType.isEquivalentTo(csType)) {
return false;
}
}
// Must match any undefined parameters to Object type.
for(int i = minParams; i < fnParamCountNoCallee; ++i) {
if(!Type.typeFor(type.parameterType(i + thisThisIndex)).isEquivalentTo(Type.OBJECT)) {
return false;
}
}
return true;
}
private static int getParamCount(final MethodType type) {
final int paramCount = type.parameterCount();
return type.parameterType(paramCount - 1).isArray() ? Integer.MAX_VALUE : paramCount;
}
private boolean canBeDeoptimized() {
return optimismInfo != null;
}
private MethodHandle createComposableInvoker(final boolean isConstructor) {
final MethodHandle handle = getInvokerOrConstructor(isConstructor);
// If compiled function is not optimistic, it can't ever change its invoker/constructor, so just return them
// directly.
if(!canBeDeoptimized()) {
return handle;
}
// Otherwise, we need a new level of indirection; need to introduce a mutable call site that can relink itself
// to the compiled function's changed target whenever the optimistic assumptions are invalidated.
final CallSite cs = new MutableCallSite(handle.type());
relinkComposableInvoker(cs, this, isConstructor);
return cs.dynamicInvoker();
}
private static class HandleAndAssumptions {
final MethodHandle handle;
final SwitchPoint assumptions;
HandleAndAssumptions(final MethodHandle handle, final SwitchPoint assumptions) {
this.handle = handle;
this.assumptions = assumptions;
}
GuardedInvocation createInvocation() {
return new GuardedInvocation(handle, assumptions);
}
}
Returns a pair of an invocation created with a passed-in supplier and a non-invalidated switch point for optimistic assumptions (or null for the switch point if the function can not be deoptimized). While the method makes a best effort to return a non-invalidated switch point (compensating for possible deoptimizing recompilation happening on another thread) it is still possible that by the time this method returns the switchpoint has been invalidated by a RewriteException
triggered on another thread for this function. This is not a problem, though, as these switch points are always used to produce call sites that fall back to relinking when they are invalidated, and in this case the execution will end up here again. What this method basically does is minimize such busy-loop relinking while the function is being recompiled on a different thread. Params: - invocationSupplier – the supplier that constructs the actual invocation method handle; should use the
CompiledFunction
method itself in some capacity.
Returns: a tuple object containing the method handle as created by the supplier and an optimistic assumptions
switch point that is guaranteed to not have been invalidated before the call to this method (or null if the
function can't be further deoptimized).
/**
* Returns a pair of an invocation created with a passed-in supplier and a non-invalidated switch point for
* optimistic assumptions (or null for the switch point if the function can not be deoptimized). While the method
* makes a best effort to return a non-invalidated switch point (compensating for possible deoptimizing
* recompilation happening on another thread) it is still possible that by the time this method returns the
* switchpoint has been invalidated by a {@code RewriteException} triggered on another thread for this function.
* This is not a problem, though, as these switch points are always used to produce call sites that fall back to
* relinking when they are invalidated, and in this case the execution will end up here again. What this method
* basically does is minimize such busy-loop relinking while the function is being recompiled on a different thread.
* @param invocationSupplier the supplier that constructs the actual invocation method handle; should use the
* {@code CompiledFunction} method itself in some capacity.
* @return a tuple object containing the method handle as created by the supplier and an optimistic assumptions
* switch point that is guaranteed to not have been invalidated before the call to this method (or null if the
* function can't be further deoptimized).
*/
private synchronized HandleAndAssumptions getValidOptimisticInvocation(final Supplier<MethodHandle> invocationSupplier) {
for(;;) {
final MethodHandle handle = invocationSupplier.get();
final SwitchPoint assumptions = canBeDeoptimized() ? optimismInfo.optimisticAssumptions : null;
if(assumptions != null && assumptions.hasBeenInvalidated()) {
// We can be in a situation where one thread is in the middle of a deoptimizing compilation when we hit
// this and thus, it has invalidated the old switch point, but hasn't created the new one yet. Note that
// the behavior of invalidating the old switch point before recompilation, and only creating the new one
// after recompilation is by design. If we didn't wait here for the recompilation to complete, we would
// be busy looping through the fallback path of the invalidated switch point, relinking the call site
// again with the same invalidated switch point, invoking the fallback, etc. stealing CPU cycles from
// the recompilation task we're dependent on. This can still happen if the switch point gets invalidated
// after we grabbed it here, in which case we'll indeed do one busy relink immediately.
try {
wait();
} catch (final InterruptedException e) {
// Intentionally ignored. There's nothing meaningful we can do if we're interrupted
}
} else {
return new HandleAndAssumptions(handle, assumptions);
}
}
}
private static void relinkComposableInvoker(final CallSite cs, final CompiledFunction inv, final boolean constructor) {
final HandleAndAssumptions handleAndAssumptions = inv.getValidOptimisticInvocation(new Supplier<MethodHandle>() {
@Override
public MethodHandle get() {
return inv.getInvokerOrConstructor(constructor);
}
});
final MethodHandle handle = handleAndAssumptions.handle;
final SwitchPoint assumptions = handleAndAssumptions.assumptions;
final MethodHandle target;
if(assumptions == null) {
target = handle;
} else {
final MethodHandle relink = MethodHandles.insertArguments(RELINK_COMPOSABLE_INVOKER, 0, cs, inv, constructor);
target = assumptions.guardWithTest(handle, MethodHandles.foldArguments(cs.dynamicInvoker(), relink));
}
cs.setTarget(target.asType(cs.type()));
}
private MethodHandle getInvokerOrConstructor(final boolean selectCtor) {
return selectCtor ? getConstructor() : createInvokerForPessimisticCaller();
}
Returns a guarded invocation for this function when not invoked as a constructor. The guarded invocation has no guard but it potentially has an optimistic assumptions switch point. As such, it will probably not be used as a final guarded invocation, but rather as a holder for an invocation handle and switch point to be decomposed and reassembled into a different final invocation by the user of this method. Any recompositions should take care to continue to use the switch point. If that is not possible, use createComposableInvoker()
instead. Returns: a guarded invocation for an ordinary (non-constructor) invocation of this function.
/**
* Returns a guarded invocation for this function when not invoked as a constructor. The guarded invocation has no
* guard but it potentially has an optimistic assumptions switch point. As such, it will probably not be used as a
* final guarded invocation, but rather as a holder for an invocation handle and switch point to be decomposed and
* reassembled into a different final invocation by the user of this method. Any recompositions should take care to
* continue to use the switch point. If that is not possible, use {@link #createComposableInvoker()} instead.
* @return a guarded invocation for an ordinary (non-constructor) invocation of this function.
*/
GuardedInvocation createFunctionInvocation(final Class<?> callSiteReturnType, final int callerProgramPoint) {
return getValidOptimisticInvocation(new Supplier<MethodHandle>() {
@Override
public MethodHandle get() {
return createInvoker(callSiteReturnType, callerProgramPoint);
}
}).createInvocation();
}
Returns a guarded invocation for this function when invoked as a constructor. The guarded invocation has no guard but it potentially has an optimistic assumptions switch point. As such, it will probably not be used as a final guarded invocation, but rather as a holder for an invocation handle and switch point to be decomposed and reassembled into a different final invocation by the user of this method. Any recompositions should take care to continue to use the switch point. If that is not possible, use createComposableConstructor()
instead. Returns: a guarded invocation for invocation of this function as a constructor.
/**
* Returns a guarded invocation for this function when invoked as a constructor. The guarded invocation has no guard
* but it potentially has an optimistic assumptions switch point. As such, it will probably not be used as a final
* guarded invocation, but rather as a holder for an invocation handle and switch point to be decomposed and
* reassembled into a different final invocation by the user of this method. Any recompositions should take care to
* continue to use the switch point. If that is not possible, use {@link #createComposableConstructor()} instead.
* @return a guarded invocation for invocation of this function as a constructor.
*/
GuardedInvocation createConstructorInvocation() {
return getValidOptimisticInvocation(new Supplier<MethodHandle>() {
@Override
public MethodHandle get() {
return getConstructor();
}
}).createInvocation();
}
private MethodHandle createInvoker(final Class<?> callSiteReturnType, final int callerProgramPoint) {
final boolean isOptimistic = canBeDeoptimized();
MethodHandle handleRewriteException = isOptimistic ? createRewriteExceptionHandler() : null;
MethodHandle inv = invoker;
if(isValid(callerProgramPoint)) {
inv = OptimisticReturnFilters.filterOptimisticReturnValue(inv, callSiteReturnType, callerProgramPoint);
inv = changeReturnType(inv, callSiteReturnType);
if(callSiteReturnType.isPrimitive() && handleRewriteException != null) {
// because handleRewriteException always returns Object
handleRewriteException = OptimisticReturnFilters.filterOptimisticReturnValue(handleRewriteException,
callSiteReturnType, callerProgramPoint);
}
} else if(isOptimistic) {
// Required so that rewrite exception has the same return type. It'd be okay to do it even if we weren't
// optimistic, but it isn't necessary as the linker upstream will eventually convert the return type.
inv = changeReturnType(inv, callSiteReturnType);
}
if(isOptimistic) {
assert handleRewriteException != null;
final MethodHandle typedHandleRewriteException = changeReturnType(handleRewriteException, inv.type().returnType());
return MH.catchException(inv, RewriteException.class, typedHandleRewriteException);
}
return inv;
}
private MethodHandle createRewriteExceptionHandler() {
return MH.foldArguments(RESTOF_INVOKER, MH.insertArguments(HANDLE_REWRITE_EXCEPTION, 0, this, optimismInfo));
}
private static MethodHandle changeReturnType(final MethodHandle mh, final Class<?> newReturnType) {
return Bootstrap.getLinkerServices().asType(mh, mh.type().changeReturnType(newReturnType));
}
@SuppressWarnings("unused")
private static MethodHandle handleRewriteException(final CompiledFunction function, final OptimismInfo oldOptimismInfo, final RewriteException re) {
return function.handleRewriteException(oldOptimismInfo, re);
}
Debug function for printing out all invalidated program points and their
invalidation mapping to next type
Params: - ipp –
Returns: string describing the ipp map
/**
* Debug function for printing out all invalidated program points and their
* invalidation mapping to next type
* @param ipp
* @return string describing the ipp map
*/
private static List<String> toStringInvalidations(final Map<Integer, Type> ipp) {
if (ipp == null) {
return Collections.emptyList();
}
final List<String> list = new ArrayList<>();
for (final Iterator<Map.Entry<Integer, Type>> iter = ipp.entrySet().iterator(); iter.hasNext(); ) {
final Map.Entry<Integer, Type> entry = iter.next();
final char bct = entry.getValue().getBytecodeStackType();
final String type;
switch (entry.getValue().getBytecodeStackType()) {
case 'A':
type = "object";
break;
case 'I':
type = "int";
break;
case 'J':
type = "long";
break;
case 'D':
type = "double";
break;
default:
type = String.valueOf(bct);
break;
}
final StringBuilder sb = new StringBuilder();
sb.append('[').
append("program point: ").
append(entry.getKey()).
append(" -> ").
append(type).
append(']');
list.add(sb.toString());
}
return list;
}
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: ", type);
log.indent();
for (final String str : toStringInvalidations(ipp)) {
log.fine(str);
}
log.unindent();
}
}
Handles a RewriteException
raised during the execution of this function by recompiling (if needed) the function with an optimistic assumption invalidated at the program point indicated by the exception, and then executing a rest-of method to complete the execution with the deoptimized version. Params: - oldOptInfo – the optimism info of this function. We must store it explicitly as a bound argument in the
method handle, otherwise it could be null for handling a rewrite exception in an outer invocation of a recursive
function when recursive invocations of the function have completely deoptimized it.
- re – the rewrite exception that was raised
Returns: the method handle for the rest-of method, for folding composition.
/**
* Handles a {@link RewriteException} raised during the execution of this function by recompiling (if needed) the
* function with an optimistic assumption invalidated at the program point indicated by the exception, and then
* executing a rest-of method to complete the execution with the deoptimized version.
* @param oldOptInfo the optimism info of this function. We must store it explicitly as a bound argument in the
* method handle, otherwise it could be null for handling a rewrite exception in an outer invocation of a recursive
* function when recursive invocations of the function have completely deoptimized it.
* @param re the rewrite exception that was raised
* @return the method handle for the rest-of method, for folding composition.
*/
private synchronized MethodHandle handleRewriteException(final OptimismInfo oldOptInfo, final RewriteException re) {
if (log.isEnabled()) {
log.info(
new RecompilationEvent(
Level.INFO,
re,
re.getReturnValueNonDestructive()),
"caught RewriteException ",
re.getMessageShort());
log.indent();
}
final MethodType type = type();
// Compiler needs a call site type as its input, which always has a callee parameter, so we must add it if
// this function doesn't have a callee parameter.
final MethodType ct = type.parameterType(0) == ScriptFunction.class ?
type :
type.insertParameterTypes(0, ScriptFunction.class);
final OptimismInfo currentOptInfo = optimismInfo;
final boolean shouldRecompile = currentOptInfo != null && currentOptInfo.requestRecompile(re);
// Effective optimism info, for subsequent use. We'll normally try to use the current (latest) one, but if it
// 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 cached = fn.isCached();
final Compiler compiler = effectiveOptInfo.getCompiler(fn, ct, re); //set to non rest-of
if (!shouldRecompile) {
// It didn't necessarily recompile, e.g. for an outer invocation of a recursive function if we already
// recompiled a deoptimized version for an inner invocation.
// We still need to do the rest of from the beginning
logRecompile("Rest-of compilation [STANDALONE] ", fn, ct, effectiveOptInfo.invalidatedProgramPoints);
return restOfHandle(effectiveOptInfo, compiler.compile(fn, cached ? CompilationPhases.COMPILE_CACHED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
}
logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, ct, effectiveOptInfo.invalidatedProgramPoints);
fn = compiler.compile(fn, cached ? CompilationPhases.RECOMPILE_CACHED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE);
log.fine("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, ct, effectiveOptInfo.invalidatedProgramPoints);
final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL);
if (effectiveOptInfo.data.usePersistentCodeCache()) {
final RecompilableScriptFunctionData data = effectiveOptInfo.data;
final int functionNodeId = data.getFunctionNodeId();
final TypeMap typeMap = data.typeMap(ct);
final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
final String cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes);
compiler.persistClassInfo(cacheKey, normalFn);
}
final boolean canBeDeoptimized = normalFn.canBeDeoptimized();
if (log.isEnabled()) {
log.unindent();
log.info("Done.");
log.info("Recompiled '", fn.getName(), "' (", Debug.id(this), ") ", canBeDeoptimized ? "can still be deoptimized." : " is completely deoptimized.");
log.finest("Looking up invoker...");
}
final MethodHandle newInvoker = effectiveOptInfo.data.lookup(fn);
invoker = newInvoker.asType(type.changeReturnType(newInvoker.type().returnType()));
constructor = null; // Will be regenerated when needed
log.info("Done: ", invoker);
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) {
effectiveOptInfo.newOptimisticAssumptions(); // Otherwise, set a new switch point.
} else {
optimismInfo = null; // If we got to a point where we no longer have optimistic assumptions, let the optimism info go.
}
notifyAll();
return restOf;
}
private MethodHandle restOfHandle(final OptimismInfo info, final FunctionNode restOfFunction, final boolean canBeDeoptimized) {
assert info != null;
assert restOfFunction.getCompileUnit().getUnitClassName().contains("restOf");
final MethodHandle restOf =
changeReturnType(
info.data.lookupCodeMethod(
restOfFunction.getCompileUnit().getCode(),
MH.type(restOfFunction.getReturnType().getTypeClass(),
RewriteException.class)),
Object.class);
if (!canBeDeoptimized) {
return restOf;
}
// If rest-of is itself optimistic, we must make sure that we can repeat a deoptimization if it, too hits an exception.
return MH.catchException(restOf, RewriteException.class, createRewriteExceptionHandler());
}
private static class OptimismInfo {
// TODO: this is pointing to its owning ScriptFunctionData. Re-evaluate if that's okay.
private final RecompilableScriptFunctionData data;
private final Map<Integer, Type> invalidatedProgramPoints;
private SwitchPoint optimisticAssumptions;
private final DebugLogger log;
OptimismInfo(final RecompilableScriptFunctionData data, final Map<Integer, Type> invalidatedProgramPoints) {
this.data = data;
this.log = data.getLogger();
this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new TreeMap<>() : invalidatedProgramPoints;
newOptimisticAssumptions();
}
private void newOptimisticAssumptions() {
optimisticAssumptions = new SwitchPoint();
}
boolean requestRecompile(final RewriteException e) {
final Type retType = e.getReturnType();
final Type previousFailedType = invalidatedProgramPoints.put(e.getProgramPoint(), retType);
if (previousFailedType != null && !previousFailedType.narrowerThan(retType)) {
final StackTraceElement[] stack = e.getStackTrace();
final String functionId = stack.length == 0 ?
data.getName() :
stack[0].getClassName() + "." + stack[0].getMethodName();
log.info("RewriteException for an already invalidated program point ", e.getProgramPoint(), " in ", functionId, ". This is okay for a recursive function invocation, but a bug otherwise.");
return false;
}
SwitchPoint.invalidateAll(new SwitchPoint[] { optimisticAssumptions });
return true;
}
Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final RewriteException e) {
return data.getCompiler(fn, actualCallSiteType, e.getRuntimeScope(), invalidatedProgramPoints, getEntryPoints(e));
}
private static int[] getEntryPoints(final RewriteException e) {
final int[] prevEntryPoints = e.getPreviousContinuationEntryPoints();
final int[] entryPoints;
if (prevEntryPoints == null) {
entryPoints = new int[1];
} else {
final int l = prevEntryPoints.length;
entryPoints = new int[l + 1];
System.arraycopy(prevEntryPoints, 0, entryPoints, 1, l);
}
entryPoints[0] = e.getProgramPoint();
return entryPoints;
}
FunctionNode reparse() {
return data.reparse();
}
}
@SuppressWarnings("unused")
private static Object newFilter(final Object result, final Object allocation) {
return (result instanceof ScriptObject || !JSType.isPrimitive(result))? result : allocation;
}
private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
return MH.findStatic(MethodHandles.lookup(), CompiledFunction.class, name, MH.type(rtype, types));
}
}