blob: c092230b51d7db0890d2323584e9d150f646ec31 [file] [log] [blame]
/*
* 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.codegen.CompilerConstants.virtualCallNoLookup;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.SwitchPoint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.LongAdder;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.support.Guards;
import jdk.nashorn.internal.codegen.ApplySpecialization;
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
import jdk.nashorn.internal.objects.Global;
import jdk.nashorn.internal.objects.NativeFunction;
import jdk.nashorn.internal.objects.annotations.SpecializedFunction.LinkLogic;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
/**
* Runtime representation of a JavaScript function. This class has only private
* and protected constructors. There are no *public* constructors - but only
* factory methods that follow the naming pattern "createXYZ".
*/
public class ScriptFunction extends ScriptObject {
/**
* Method handle for prototype getter for this ScriptFunction
*/
public static final MethodHandle G$PROTOTYPE = findOwnMH_S("G$prototype", Object.class, Object.class);
/**
* Method handle for prototype setter for this ScriptFunction
*/
public static final MethodHandle S$PROTOTYPE = findOwnMH_S("S$prototype", void.class, Object.class, Object.class);
/**
* Method handle for length getter for this ScriptFunction
*/
public static final MethodHandle G$LENGTH = findOwnMH_S("G$length", int.class, Object.class);
/**
* Method handle for name getter for this ScriptFunction
*/
public static final MethodHandle G$NAME = findOwnMH_S("G$name", Object.class, Object.class);
/**
* Method handle used for implementing sync() in mozilla_compat
*/
public static final MethodHandle INVOKE_SYNC = findOwnMH_S("invokeSync", Object.class, ScriptFunction.class, Object.class, Object.class, Object[].class);
/**
* Method handle for allocate function for this ScriptFunction
*/
static final MethodHandle ALLOCATE = findOwnMH_V("allocate", Object.class);
private static final MethodHandle WRAPFILTER = findOwnMH_S("wrapFilter", Object.class, Object.class);
private static final MethodHandle SCRIPTFUNCTION_GLOBALFILTER = findOwnMH_S("globalFilter", Object.class, Object.class);
/**
* method handle to scope getter for this ScriptFunction
*/
public static final Call GET_SCOPE = virtualCallNoLookup(ScriptFunction.class, "getScope", ScriptObject.class);
private static final MethodHandle IS_FUNCTION_MH = findOwnMH_S("isFunctionMH", boolean.class, Object.class, ScriptFunctionData.class);
private static final MethodHandle IS_APPLY_FUNCTION = findOwnMH_S("isApplyFunction", boolean.class, boolean.class, Object.class, Object.class);
private static final MethodHandle IS_NONSTRICT_FUNCTION = findOwnMH_S("isNonStrictFunction", boolean.class, Object.class, Object.class, ScriptFunctionData.class);
private static final MethodHandle ADD_ZEROTH_ELEMENT = findOwnMH_S("addZerothElement", Object[].class, Object[].class, Object.class);
private static final MethodHandle WRAP_THIS = MH.findStatic(MethodHandles.lookup(), ScriptFunctionData.class, "wrapThis", MH.type(Object.class, Object.class));
// various property maps used for different kinds of functions
// property map for anonymous function that serves as Function.prototype
private static final PropertyMap anonmap$;
// property map for strict mode functions
private static final PropertyMap strictmodemap$;
// property map for bound functions
private static final PropertyMap boundfunctionmap$;
// property map for non-strict, non-bound functions.
private static final PropertyMap map$;
// Marker object for lazily initialized prototype object
private static final Object LAZY_PROTOTYPE = new Object();
private static PropertyMap createStrictModeMap(final PropertyMap map) {
final int flags = Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE;
PropertyMap newMap = map;
// Need to add properties directly to map since slots are assigned speculatively by newUserAccessors.
newMap = newMap.addPropertyNoHistory(map.newUserAccessors("arguments", flags));
newMap = newMap.addPropertyNoHistory(map.newUserAccessors("caller", flags));
return newMap;
}
private static PropertyMap createBoundFunctionMap(final PropertyMap strictModeMap) {
// Bound function map is same as strict function map, but additionally lacks the "prototype" property, see
// ECMAScript 5.1 section 15.3.4.5
return strictModeMap.deleteProperty(strictModeMap.findProperty("prototype"));
}
static {
anonmap$ = PropertyMap.newMap();
final ArrayList<Property> properties = new ArrayList<>(3);
properties.add(AccessorProperty.create("prototype", Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, G$PROTOTYPE, S$PROTOTYPE));
properties.add(AccessorProperty.create("length", Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE | Property.NOT_WRITABLE, G$LENGTH, null));
properties.add(AccessorProperty.create("name", Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE | Property.NOT_WRITABLE, G$NAME, null));
map$ = PropertyMap.newMap(properties);
strictmodemap$ = createStrictModeMap(map$);
boundfunctionmap$ = createBoundFunctionMap(strictmodemap$);
}
private static boolean isStrict(final int flags) {
return (flags & ScriptFunctionData.IS_STRICT) != 0;
}
// Choose the map based on strict mode!
private static PropertyMap getMap(final boolean strict) {
return strict ? strictmodemap$ : map$;
}
/**
* The parent scope.
*/
private final ScriptObject scope;
private final ScriptFunctionData data;
/**
* The property map used for newly allocated object when function is used as
* constructor.
*/
protected PropertyMap allocatorMap;
/**
* Reference to constructor prototype.
*/
protected Object prototype;
/**
* Constructor
*
* @param data static function data
* @param map property map
* @param scope scope
*/
private ScriptFunction(
final ScriptFunctionData data,
final PropertyMap map,
final ScriptObject scope,
final Global global) {
super(map);
if (Context.DEBUG) {
constructorCount.increment();
}
this.data = data;
this.scope = scope;
this.setInitialProto(global.getFunctionPrototype());
this.prototype = LAZY_PROTOTYPE;
// We have to fill user accessor functions late as these are stored
// in this object rather than in the PropertyMap of this object.
assert objectSpill == null;
if (isStrict() || isBoundFunction()) {
final ScriptFunction typeErrorThrower = global.getTypeErrorThrower();
initUserAccessors("arguments", Property.NOT_CONFIGURABLE | Property.NOT_ENUMERABLE, typeErrorThrower, typeErrorThrower);
initUserAccessors("caller", Property.NOT_CONFIGURABLE | Property.NOT_ENUMERABLE, typeErrorThrower, typeErrorThrower);
}
}
/**
* Constructor
*
* @param name function name
* @param methodHandle method handle to function (if specializations are
* present, assumed to be most generic)
* @param map property map
* @param scope scope
* @param specs specialized version of this function - other method handles
* @param flags {@link ScriptFunctionData} flags
*/
private ScriptFunction(
final String name,
final MethodHandle methodHandle,
final PropertyMap map,
final ScriptObject scope,
final Specialization[] specs,
final int flags,
final Global global) {
this(new FinalScriptFunctionData(name, methodHandle, specs, flags), map, scope, global);
}
/**
* Constructor
*
* @param name name of function
* @param methodHandle handle for invocation
* @param scope scope object
* @param specs specialized versions of this method, if available, null
* otherwise
* @param flags {@link ScriptFunctionData} flags
*/
private ScriptFunction(
final String name,
final MethodHandle methodHandle,
final ScriptObject scope,
final Specialization[] specs,
final int flags) {
this(name, methodHandle, getMap(isStrict(flags)), scope, specs, flags, Global.instance());
}
/**
* Constructor called by Nasgen generated code, zero added members, use the
* default map. Creates builtin functions only.
*
* @param name name of function
* @param invokeHandle handle for invocation
* @param specs specialized versions of this method, if available, null
* otherwise
*/
protected ScriptFunction(final String name, final MethodHandle invokeHandle, final Specialization[] specs) {
this(name, invokeHandle, map$, null, specs, ScriptFunctionData.IS_BUILTIN_CONSTRUCTOR, Global.instance());
}
/**
* Constructor called by Nasgen generated code, non zero member count, use
* the map passed as argument. Creates builtin functions only.
*
* @param name name of function
* @param invokeHandle handle for invocation
* @param map initial property map
* @param specs specialized versions of this method, if available, null
* otherwise
*/
protected ScriptFunction(final String name, final MethodHandle invokeHandle, final PropertyMap map, final Specialization[] specs) {
this(name, invokeHandle, map.addAll(map$), null, specs, ScriptFunctionData.IS_BUILTIN_CONSTRUCTOR, Global.instance());
}
// Factory methods to create various functions
/**
* Factory method called by compiler generated code for functions that need
* parent scope.
*
* @param constants the generated class' constant array
* @param index the index of the {@code RecompilableScriptFunctionData}
* object in the constants array.
* @param scope the parent scope object
* @return a newly created function object
*/
public static ScriptFunction create(final Object[] constants, final int index, final ScriptObject scope) {
final RecompilableScriptFunctionData data = (RecompilableScriptFunctionData) constants[index];
return new ScriptFunction(data, getMap(data.isStrict()), scope, Global.instance());
}
/**
* Factory method called by compiler generated code for functions that don't
* need parent scope.
*
* @param constants the generated class' constant array
* @param index the index of the {@code RecompilableScriptFunctionData}
* object in the constants array.
* @return a newly created function object
*/
public static ScriptFunction create(final Object[] constants, final int index) {
return create(constants, index, null);
}
/**
* Create anonymous function that serves as Function.prototype
*
* @return anonymous function object
*/
public static ScriptFunction createAnonymous() {
return new ScriptFunction("", GlobalFunctions.ANONYMOUS, anonmap$, null);
}
// builtin function create helper factory
private static ScriptFunction createBuiltin(final String name, final MethodHandle methodHandle, final Specialization[] specs, final int flags) {
final ScriptFunction func = new ScriptFunction(name, methodHandle, null, specs, flags);
func.setPrototype(UNDEFINED);
// Non-constructor built-in functions do not have "prototype" property
func.deleteOwnProperty(func.getMap().findProperty("prototype"));
return func;
}
/**
* Factory method for non-constructor built-in functions
*
* @param name function name
* @param methodHandle handle for invocation
* @param specs specialized versions of function if available, null
* otherwise
* @return new ScriptFunction
*/
public static ScriptFunction createBuiltin(final String name, final MethodHandle methodHandle, final Specialization[] specs) {
return ScriptFunction.createBuiltin(name, methodHandle, specs, ScriptFunctionData.IS_BUILTIN);
}
/**
* Factory method for non-constructor built-in functions
*
* @param name function name
* @param methodHandle handle for invocation
* @return new ScriptFunction
*/
public static ScriptFunction createBuiltin(final String name, final MethodHandle methodHandle) {
return ScriptFunction.createBuiltin(name, methodHandle, null);
}
/**
* Factory method for non-constructor built-in, strict functions
*
* @param name function name
* @param methodHandle handle for invocation
* @return new ScriptFunction
*/
public static ScriptFunction createStrictBuiltin(final String name, final MethodHandle methodHandle) {
return ScriptFunction.createBuiltin(name, methodHandle, null, ScriptFunctionData.IS_BUILTIN | ScriptFunctionData.IS_STRICT);
}
// Subclass to represent bound functions
private static class Bound extends ScriptFunction {
private final ScriptFunction target;
Bound(final ScriptFunctionData boundData, final ScriptFunction target) {
super(boundData, boundfunctionmap$, null, Global.instance());
setPrototype(ScriptRuntime.UNDEFINED);
this.target = target;
}
@Override
protected ScriptFunction getTargetFunction() {
return target;
}
}
/**
* Creates a version of this function bound to a specific "self" and other
* arguments, as per {@code Function.prototype.bind} functionality in
* ECMAScript 5.1 section 15.3.4.5.
*
* @param self the self to bind to this function. Can be null (in which
* case, null is bound as this).
* @param args additional arguments to bind to this function. Can be null or
* empty to not bind additional arguments.
* @return a function with the specified self and parameters bound.
*/
public final ScriptFunction createBound(final Object self, final Object[] args) {
return new Bound(data.makeBoundFunctionData(this, self, args), getTargetFunction());
}
/**
* Create a function that invokes this function synchronized on {@code sync}
* or the self object of the invocation.
*
* @param sync the Object to synchronize on, or undefined
* @return synchronized function
*/
public final ScriptFunction createSynchronized(final Object sync) {
final MethodHandle mh = MH.insertArguments(ScriptFunction.INVOKE_SYNC, 0, this, sync);
return createBuiltin(getName(), mh);
}
@Override
public String getClassName() {
return "Function";
}
/**
* ECMA 15.3.5.3 [[HasInstance]] (V) Step 3 if "prototype" value is not an
* Object, throw TypeError
*/
@Override
public boolean isInstance(final ScriptObject instance) {
final Object basePrototype = getTargetFunction().getPrototype();
if (!(basePrototype instanceof ScriptObject)) {
throw typeError("prototype.not.an.object", ScriptRuntime.safeToString(getTargetFunction()), ScriptRuntime.safeToString(basePrototype));
}
for (ScriptObject proto = instance.getProto(); proto != null; proto = proto.getProto()) {
if (proto == basePrototype) {
return true;
}
}
return false;
}
/**
* Returns the target function for this function. If the function was not
* created using {@link #createBound(Object, Object[])}, its target
* function is itself. If it is bound, its target function is the target
* function of the function it was made from (therefore, the target function
* is always the final, unbound recipient of the calls).
*
* @return the target function for this function.
*/
protected ScriptFunction getTargetFunction() {
return this;
}
final boolean isBoundFunction() {
return getTargetFunction() != this;
}
/**
* Set the arity of this ScriptFunction
*
* @param arity arity
*/
public final void setArity(final int arity) {
data.setArity(arity);
}
/**
* Is this a ECMAScript 'use strict' function?
*
* @return true if function is in strict mode
*/
public final boolean isStrict() {
return data.isStrict();
}
/**
* Returns true if this is a non-strict, non-built-in function that requires
* non-primitive this argument according to ECMA 10.4.3.
*
* @return true if this argument must be an object
*/
public final boolean needsWrappedThis() {
return data.needsWrappedThis();
}
private static boolean needsWrappedThis(final Object fn) {
return fn instanceof ScriptFunction ? ((ScriptFunction) fn).needsWrappedThis() : false;
}
/**
* Execute this script function.
*
* @param self Target object.
* @param arguments Call arguments.
* @return ScriptFunction result.
* @throws Throwable if there is an exception/error with the invocation or
* thrown from it
*/
final Object invoke(final Object self, final Object... arguments) throws Throwable {
if (Context.DEBUG) {
invokes.increment();
}
return data.invoke(this, self, arguments);
}
/**
* Execute this script function as a constructor.
*
* @param arguments Call arguments.
* @return Newly constructed result.
* @throws Throwable if there is an exception/error with the invocation or
* thrown from it
*/
final Object construct(final Object... arguments) throws Throwable {
return data.construct(this, arguments);
}
/**
* Allocate function. Called from generated {@link ScriptObject} code for
* allocation as a factory method
*
* @return a new instance of the {@link ScriptObject} whose allocator this
* is
*/
@SuppressWarnings("unused")
private Object allocate() {
if (Context.DEBUG) {
allocations.increment();
}
assert !isBoundFunction(); // allocate never invoked on bound functions
final ScriptObject prototype = getAllocatorPrototype();
final ScriptObject object = data.allocate(getAllocatorMap(prototype));
if (object != null) {
object.setInitialProto(prototype);
}
return object;
}
/**
* Get the property map used by "allocate"
* @param prototype actual prototype object
* @return property map
*/
private synchronized PropertyMap getAllocatorMap(final ScriptObject prototype) {
if (allocatorMap == null || allocatorMap.isInvalidSharedMapFor(prototype)) {
// The prototype map has changed since this function was last used as constructor.
// Get a new allocator map.
allocatorMap = data.getAllocatorMap(prototype);
}
return allocatorMap;
}
/**
* Return the actual prototype used by "allocate"
* @return allocator prototype
*/
private ScriptObject getAllocatorPrototype() {
final Object prototype = getPrototype();
if (prototype instanceof ScriptObject) {
return (ScriptObject) prototype;
}
return Global.objectPrototype();
}
@Override
public final String safeToString() {
return toSource();
}
@Override
public final String toString() {
return data.toString();
}
/**
* Get this function as a String containing its source code. If no source
* code exists in this ScriptFunction, its contents will be displayed as
* {@code [native code]}
*
* @return string representation of this function's source
*/
public final String toSource() {
return data.toSource();
}
/**
* Get the prototype object for this function
*
* @return prototype
*/
public final Object getPrototype() {
if (prototype == LAZY_PROTOTYPE) {
prototype = new PrototypeObject(this);
}
return prototype;
}
/**
* Set the prototype object for this function
*
* @param newPrototype new prototype object
*/
public synchronized final void setPrototype(final Object newPrototype) {
if (newPrototype instanceof ScriptObject && newPrototype != this.prototype && allocatorMap != null) {
// Unset allocator map to be replaced with one matching the new prototype.
allocatorMap = null;
}
this.prototype = newPrototype;
}
/**
* Return the invoke handle bound to a given ScriptObject self reference. If
* callee parameter is required result is rebound to this.
*
* @param self self reference
* @return bound invoke handle
*/
public final MethodHandle getBoundInvokeHandle(final Object self) {
return MH.bindTo(bindToCalleeIfNeeded(data.getGenericInvoker(scope)), self);
}
/**
* Bind the method handle to this {@code ScriptFunction} instance if it
* needs a callee parameter. If this function's method handles don't have a
* callee parameter, the handle is returned unchanged.
*
* @param methodHandle the method handle to potentially bind to this
* function instance.
* @return the potentially bound method handle
*/
private MethodHandle bindToCalleeIfNeeded(final MethodHandle methodHandle) {
return ScriptFunctionData.needsCallee(methodHandle) ? MH.bindTo(methodHandle, this) : methodHandle;
}
/**
* Get the name for this function
*
* @return the name
*/
public final String getName() {
return data.getName();
}
/**
* Get the scope for this function
*
* @return the scope
*/
public final ScriptObject getScope() {
return scope;
}
/**
* Prototype getter for this ScriptFunction - follows the naming convention
* used by Nasgen and the code generator
*
* @param self self reference
* @return self's prototype
*/
public static Object G$prototype(final Object self) {
return self instanceof ScriptFunction
? ((ScriptFunction) self).getPrototype()
: UNDEFINED;
}
/**
* Prototype setter for this ScriptFunction - follows the naming convention
* used by Nasgen and the code generator
*
* @param self self reference
* @param prototype prototype to set
*/
public static void S$prototype(final Object self, final Object prototype) {
if (self instanceof ScriptFunction) {
((ScriptFunction) self).setPrototype(prototype);
}
}
/**
* Length getter - ECMA 15.3.3.2: Function.length
*
* @param self self reference
* @return length
*/
public static int G$length(final Object self) {
if (self instanceof ScriptFunction) {
return ((ScriptFunction) self).data.getArity();
}
return 0;
}
/**
* Name getter - ECMA Function.name
*
* @param self self refence
* @return the name, or undefined if none
*/
public static Object G$name(final Object self) {
if (self instanceof ScriptFunction) {
return ((ScriptFunction) self).getName();
}
return UNDEFINED;
}
/**
* Get the prototype for this ScriptFunction
*
* @param constructor constructor
* @return prototype, or null if given constructor is not a ScriptFunction
*/
public static ScriptObject getPrototype(final ScriptFunction constructor) {
if (constructor != null) {
final Object proto = constructor.getPrototype();
if (proto instanceof ScriptObject) {
return (ScriptObject) proto;
}
}
return null;
}
// These counters are updated only in debug mode.
private static LongAdder constructorCount;
private static LongAdder invokes;
private static LongAdder allocations;
static {
if (Context.DEBUG) {
constructorCount = new LongAdder();
invokes = new LongAdder();
allocations = new LongAdder();
}
}
/**
* @return the constructorCount
*/
public static long getConstructorCount() {
return constructorCount.longValue();
}
/**
* @return the invokes
*/
public static long getInvokes() {
return invokes.longValue();
}
/**
* @return the allocations
*/
public static long getAllocations() {
return allocations.longValue();
}
@Override
protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) {
final MethodType type = desc.getMethodType();
assert desc.getMethodType().returnType() == Object.class && !NashornCallSiteDescriptor.isOptimistic(desc);
final CompiledFunction cf = data.getBestConstructor(type, scope, CompiledFunction.NO_FUNCTIONS);
final GuardedInvocation bestCtorInv = cf.createConstructorInvocation();
//TODO - ClassCastException
return new GuardedInvocation(pairArguments(bestCtorInv.getInvocation(), type), getFunctionGuard(this, cf.getFlags()), bestCtorInv.getSwitchPoints(), null);
}
private static Object wrapFilter(final Object obj) {
if (obj instanceof ScriptObject || !ScriptFunctionData.isPrimitiveThis(obj)) {
return obj;
}
return Context.getGlobal().wrapAsObject(obj);
}
@SuppressWarnings("unused")
private static Object globalFilter(final Object object) {
// replace whatever we get with the current global object
return Context.getGlobal();
}
/**
* Some receivers are primitive, in that case, according to the Spec we
* create a new native object per callsite with the wrap filter. We can only
* apply optimistic builtins if there is no per instance state saved for
* these wrapped objects (e.g. currently NativeStrings), otherwise we can't
* create optimistic versions
*
* @param self receiver
* @param linkLogicClass linkLogicClass, or null if no link logic exists
* @return link logic instance, or null if one could not be constructed for
* this receiver
*/
private static LinkLogic getLinkLogic(final Object self, final Class<? extends LinkLogic> linkLogicClass) {
if (linkLogicClass == null) {
return LinkLogic.EMPTY_INSTANCE; //always OK to link this, specialization but without special linking logic
}
if (!Context.getContextTrusted().getEnv()._optimistic_types) {
return null; //if optimistic types are off, optimistic builtins are too
}
final Object wrappedSelf = wrapFilter(self);
if (wrappedSelf instanceof OptimisticBuiltins) {
if (wrappedSelf != self && ((OptimisticBuiltins) wrappedSelf).hasPerInstanceAssumptions()) {
return null; //pessimistic - we created a wrapped object different from the primitive, but the assumptions have instance state
}
return ((OptimisticBuiltins) wrappedSelf).getLinkLogic(linkLogicClass);
}
return null;
}
/**
* dyn:call call site signature: (callee, thiz, [args...]) generated method
* signature: (callee, thiz, [args...])
*
* cases:
* (a) method has callee parameter
* (1) for local/scope calls, we just bind thiz and drop the second argument.
* (2) for normal this-calls, we have to swap thiz and callee to get matching signatures.
* (b) method doesn't have callee parameter (builtin functions)
* (3) for local/scope calls, bind thiz and drop both callee and thiz.
* (4) for normal this-calls, drop callee.
*
* @return guarded invocation for call
*/
@Override
protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final LinkRequest request) {
final MethodType type = desc.getMethodType();
final String name = getName();
final boolean isUnstable = request.isCallSiteUnstable();
final boolean scopeCall = NashornCallSiteDescriptor.isScope(desc);
final boolean isCall = !scopeCall && data.isBuiltin() && "call".equals(name);
final boolean isApply = !scopeCall && data.isBuiltin() && "apply".equals(name);
final boolean isApplyOrCall = isCall | isApply;
if (isUnstable && !isApplyOrCall) {
//megamorphic - replace call with apply
final MethodHandle handle;
//ensure that the callsite is vararg so apply can consume it
if (type.parameterCount() == 3 && type.parameterType(2) == Object[].class) {
// Vararg call site
handle = ScriptRuntime.APPLY.methodHandle();
} else {
// (callee, this, args...) => (callee, this, args[])
handle = MH.asCollector(ScriptRuntime.APPLY.methodHandle(), Object[].class, type.parameterCount() - 2);
}
// If call site is statically typed to take a ScriptFunction, we don't need a guard, otherwise we need a
// generic "is this a ScriptFunction?" guard.
return new GuardedInvocation(
handle,
null,
(SwitchPoint) null,
ClassCastException.class);
}
MethodHandle boundHandle;
MethodHandle guard = null;
// Special handling of Function.apply and Function.call. Note we must be invoking
if (isApplyOrCall && !isUnstable) {
final Object[] args = request.getArguments();
if (Bootstrap.isCallable(args[1])) {
return createApplyOrCallCall(isApply, desc, request, args);
}
} //else just fall through and link as ordinary function or unstable apply
int programPoint = INVALID_PROGRAM_POINT;
if (NashornCallSiteDescriptor.isOptimistic(desc)) {
programPoint = NashornCallSiteDescriptor.getProgramPoint(desc);
}
CompiledFunction cf = data.getBestInvoker(type, scope, CompiledFunction.NO_FUNCTIONS);
final Object self = request.getArguments()[1];
final Collection<CompiledFunction> forbidden = new HashSet<>();
//check for special fast versions of the compiled function
final List<SwitchPoint> sps = new ArrayList<>();
Class<? extends Throwable> exceptionGuard = null;
while (cf.isSpecialization()) {
final Class<? extends LinkLogic> linkLogicClass = cf.getLinkLogicClass();
//if linklogic is null, we can always link with the standard mechanism, it's still a specialization
final LinkLogic linkLogic = getLinkLogic(self, linkLogicClass);
if (linkLogic != null && linkLogic.checkLinkable(self, desc, request)) {
final DebugLogger log = Context.getContextTrusted().getLogger(Compiler.class);
if (log.isEnabled()) {
log.info("Linking optimistic builtin function: '", name, "' args=", Arrays.toString(request.getArguments()), " desc=", desc);
}
exceptionGuard = linkLogic.getRelinkException();
break;
}
//could not link this specialization because link check failed
forbidden.add(cf);
final CompiledFunction oldCf = cf;
cf = data.getBestInvoker(type, scope, forbidden);
assert oldCf != cf;
}
final GuardedInvocation bestInvoker = cf.createFunctionInvocation(type.returnType(), programPoint);
final MethodHandle callHandle = bestInvoker.getInvocation();
if (data.needsCallee()) {
if (scopeCall && needsWrappedThis()) {
// (callee, this, args...) => (callee, [this], args...)
boundHandle = MH.filterArguments(callHandle, 1, SCRIPTFUNCTION_GLOBALFILTER);
} else {
// It's already (callee, this, args...), just what we need
boundHandle = callHandle;
}
} else if (data.isBuiltin() && "extend".equals(data.getName())) {
// NOTE: the only built-in named "extend" is NativeJava.extend. As a special-case we're binding the
// current lookup as its "this" so it can do security-sensitive creation of adapter classes.
boundHandle = MH.dropArguments(MH.bindTo(callHandle, desc.getLookup()), 0, type.parameterType(0), type.parameterType(1));
} else if (scopeCall && needsWrappedThis()) {
// Make a handle that drops the passed "this" argument and substitutes either Global or Undefined
// (this, args...) => ([this], args...)
boundHandle = MH.filterArguments(callHandle, 0, SCRIPTFUNCTION_GLOBALFILTER);
// ([this], args...) => ([callee], [this], args...)
boundHandle = MH.dropArguments(boundHandle, 0, type.parameterType(0));
} else {
// (this, args...) => ([callee], this, args...)
boundHandle = MH.dropArguments(callHandle, 0, type.parameterType(0));
}
// For non-strict functions, check whether this-object is primitive type.
// If so add a to-object-wrapper argument filter.
// Else install a guard that will trigger a relink when the argument becomes primitive.
if (!scopeCall && needsWrappedThis()) {
if (ScriptFunctionData.isPrimitiveThis(request.getArguments()[1])) {
boundHandle = MH.filterArguments(boundHandle, 1, WRAPFILTER);
} else {
guard = getNonStrictFunctionGuard(this);
}
}
// Is this an unstable callsite which was earlier apply-to-call optimized?
// If so, earlier apply2call would have exploded arguments. We have to convert
// that as an array again!
if (isUnstable && NashornCallSiteDescriptor.isApplyToCall(desc)) {
boundHandle = MH.asCollector(boundHandle, Object[].class, type.parameterCount() - 2);
}
boundHandle = pairArguments(boundHandle, type);
if (bestInvoker.getSwitchPoints() != null) {
sps.addAll(Arrays.asList(bestInvoker.getSwitchPoints()));
}
final SwitchPoint[] spsArray = sps.isEmpty() ? null : sps.toArray(new SwitchPoint[sps.size()]);
return new GuardedInvocation(
boundHandle,
guard == null ?
getFunctionGuard(
this,
cf.getFlags()) :
guard,
spsArray,
exceptionGuard);
}
private GuardedInvocation createApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc, final LinkRequest request, final Object[] args) {
final MethodType descType = desc.getMethodType();
final int paramCount = descType.parameterCount();
if (descType.parameterType(paramCount - 1).isArray()) {
// This is vararg invocation of apply or call. This can normally only happen when we do a recursive
// invocation of createApplyOrCallCall (because we're doing apply-of-apply). In this case, create delegate
// linkage by unpacking the vararg invocation and use pairArguments to introduce the necessary spreader.
return createVarArgApplyOrCallCall(isApply, desc, request, args);
}
final boolean passesThis = paramCount > 2;
final boolean passesArgs = paramCount > 3;
final int realArgCount = passesArgs ? paramCount - 3 : 0;
final Object appliedFn = args[1];
final boolean appliedFnNeedsWrappedThis = needsWrappedThis(appliedFn);
//box call back to apply
CallSiteDescriptor appliedDesc = desc;
final SwitchPoint applyToCallSwitchPoint = Global.getBuiltinFunctionApplySwitchPoint();
//enough to change the proto switchPoint here
final boolean isApplyToCall = NashornCallSiteDescriptor.isApplyToCall(desc);
final boolean isFailedApplyToCall = isApplyToCall && applyToCallSwitchPoint.hasBeenInvalidated();
// R(apply|call, ...) => R(...)
MethodType appliedType = descType.dropParameterTypes(0, 1);
if (!passesThis) {
// R() => R(this)
appliedType = appliedType.insertParameterTypes(1, Object.class);
} else if (appliedFnNeedsWrappedThis) {
appliedType = appliedType.changeParameterType(1, Object.class);
}
/*
* dropArgs is a synthetic method handle that contains any args that we need to
* get rid of that come after the arguments array in the apply case. We adapt
* the callsite to ask for 3 args only and then dropArguments on the method handle
* to make it fit the extraneous args.
*/
MethodType dropArgs = MH.type(void.class);
if (isApply && !isFailedApplyToCall) {
final int pc = appliedType.parameterCount();
for (int i = 3; i < pc; i++) {
dropArgs = dropArgs.appendParameterTypes(appliedType.parameterType(i));
}
if (pc > 3) {
appliedType = appliedType.dropParameterTypes(3, pc);
}
}
if (isApply || isFailedApplyToCall) {
if (passesArgs) {
// R(this, args) => R(this, Object[])
appliedType = appliedType.changeParameterType(2, Object[].class);
// drop any extraneous arguments for the apply fail case
if (isFailedApplyToCall) {
appliedType = appliedType.dropParameterTypes(3, paramCount - 1);
}
} else {
// R(this) => R(this, Object[])
appliedType = appliedType.insertParameterTypes(2, Object[].class);
}
}
appliedDesc = appliedDesc.changeMethodType(appliedType); //no extra args
// Create the same arguments for the delegate linking request that would be passed in an actual apply'd invocation
final Object[] appliedArgs = new Object[isApply ? 3 : appliedType.parameterCount()];
appliedArgs[0] = appliedFn;
appliedArgs[1] = passesThis ? appliedFnNeedsWrappedThis ? ScriptFunctionData.wrapThis(args[2]) : args[2] : ScriptRuntime.UNDEFINED;
if (isApply && !isFailedApplyToCall) {
appliedArgs[2] = passesArgs ? NativeFunction.toApplyArgs(args[3]) : ScriptRuntime.EMPTY_ARRAY;
} else {
if (passesArgs) {
if (isFailedApplyToCall) {
final Object[] tmp = new Object[args.length - 3];
System.arraycopy(args, 3, tmp, 0, tmp.length);
appliedArgs[2] = NativeFunction.toApplyArgs(tmp);
} else {
assert !isApply;
System.arraycopy(args, 3, appliedArgs, 2, args.length - 3);
}
} else if (isFailedApplyToCall) {
appliedArgs[2] = ScriptRuntime.EMPTY_ARRAY;
}
}
// Ask the linker machinery for an invocation of the target function
final LinkRequest appliedRequest = request.replaceArguments(appliedDesc, appliedArgs);
GuardedInvocation appliedInvocation;
try {
appliedInvocation = Bootstrap.getLinkerServices().getGuardedInvocation(appliedRequest);
} catch (final RuntimeException | Error e) {
throw e;
} catch (final Exception e) {
throw new RuntimeException(e);
}
assert appliedRequest != null; // Bootstrap.isCallable() returned true for args[1], so it must produce a linkage.
final Class<?> applyFnType = descType.parameterType(0);
MethodHandle inv = appliedInvocation.getInvocation(); //method handle from apply invocation. the applied function invocation
if (isApply && !isFailedApplyToCall) {
if (passesArgs) {
// Make sure that the passed argArray is converted to Object[] the same way NativeFunction.apply() would do it.
inv = MH.filterArguments(inv, 2, NativeFunction.TO_APPLY_ARGS);
} else {
// If the original call site doesn't pass argArray, pass in an empty array
inv = MH.insertArguments(inv, 2, (Object) ScriptRuntime.EMPTY_ARRAY);
}
}
if (isApplyToCall) {
if (isFailedApplyToCall) {
//take the real arguments that were passed to a call and force them into the apply instead
Context.getContextTrusted().getLogger(ApplySpecialization.class).info("Collection arguments to revert call to apply in " + appliedFn);
inv = MH.asCollector(inv, Object[].class, realArgCount);
} else {
appliedInvocation = appliedInvocation.addSwitchPoint(applyToCallSwitchPoint);
}
}
if (!passesThis) {
// If the original call site doesn't pass in a thisArg, pass in Global/undefined as needed
inv = bindImplicitThis(appliedFn, inv);
} else if (appliedFnNeedsWrappedThis) {
// target function needs a wrapped this, so make sure we filter for that
inv = MH.filterArguments(inv, 1, WRAP_THIS);
}
inv = MH.dropArguments(inv, 0, applyFnType);
/*
* Dropargs can only be non-()V in the case of isApply && !isFailedApplyToCall, which
* is when we need to add arguments to the callsite to catch and ignore the synthetic
* extra args that someone has added to the command line.
*/
for (int i = 0; i < dropArgs.parameterCount(); i++) {
inv = MH.dropArguments(inv, 4 + i, dropArgs.parameterType(i));
}
MethodHandle guard = appliedInvocation.getGuard();
// If the guard checks the value of "this" but we aren't passing thisArg, insert the default one
if (!passesThis && guard.type().parameterCount() > 1) {
guard = bindImplicitThis(appliedFn, guard);
}
final MethodType guardType = guard.type();
// We need to account for the dropped (apply|call) function argument.
guard = MH.dropArguments(guard, 0, descType.parameterType(0));
// Take the "isApplyFunction" guard, and bind it to this function.
MethodHandle applyFnGuard = MH.insertArguments(IS_APPLY_FUNCTION, 2, this); //TODO replace this with switchpoint
// Adapt the guard to receive all the arguments that the original guard does.
applyFnGuard = MH.dropArguments(applyFnGuard, 2, guardType.parameterArray());
// Fold the original function guard into our apply guard.
guard = MH.foldArguments(applyFnGuard, guard);
return appliedInvocation.replaceMethods(inv, guard);
}
/*
* This method is used for linking nested apply. Specialized apply and call linking will create a variable arity
* call site for an apply call; when createApplyOrCallCall sees a linking request for apply or call with
* Nashorn-style variable arity call site (last argument type is Object[]) it'll delegate to this method.
* This method converts the link request from a vararg to a non-vararg one (unpacks the array), then delegates back
* to createApplyOrCallCall (with which it is thus mutually recursive), and adds appropriate argument spreaders to
* invocation and the guard of whatever createApplyOrCallCall returned to adapt it back into a variable arity
* invocation. It basically reduces the problem of vararg call site linking of apply and call back to the (already
* solved by createApplyOrCallCall) non-vararg call site linking.
*/
private GuardedInvocation createVarArgApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc,
final LinkRequest request, final Object[] args) {
final MethodType descType = desc.getMethodType();
final int paramCount = descType.parameterCount();
final Object[] varArgs = (Object[]) args[paramCount - 1];
// -1 'cause we're not passing the vararg array itself
final int copiedArgCount = args.length - 1;
final int varArgCount = varArgs.length;
// Spread arguments for the delegate createApplyOrCallCall invocation.
final Object[] spreadArgs = new Object[copiedArgCount + varArgCount];
System.arraycopy(args, 0, spreadArgs, 0, copiedArgCount);
System.arraycopy(varArgs, 0, spreadArgs, copiedArgCount, varArgCount);
// Spread call site descriptor for the delegate createApplyOrCallCall invocation. We drop vararg array and
// replace it with a list of Object.class.
final MethodType spreadType = descType.dropParameterTypes(paramCount - 1, paramCount).appendParameterTypes(
Collections.<Class<?>>nCopies(varArgCount, Object.class));
final CallSiteDescriptor spreadDesc = desc.changeMethodType(spreadType);
// Delegate back to createApplyOrCallCall with the spread (that is, reverted to non-vararg) request/
final LinkRequest spreadRequest = request.replaceArguments(spreadDesc, spreadArgs);
final GuardedInvocation spreadInvocation = createApplyOrCallCall(isApply, spreadDesc, spreadRequest, spreadArgs);
// Add spreader combinators to returned invocation and guard.
return spreadInvocation.replaceMethods(
// Use standard ScriptObject.pairArguments on the invocation
pairArguments(spreadInvocation.getInvocation(), descType),
// Use our specialized spreadGuardArguments on the guard (see below).
spreadGuardArguments(spreadInvocation.getGuard(), descType));
}
private static MethodHandle spreadGuardArguments(final MethodHandle guard, final MethodType descType) {
final MethodType guardType = guard.type();
final int guardParamCount = guardType.parameterCount();
final int descParamCount = descType.parameterCount();
final int spreadCount = guardParamCount - descParamCount + 1;
if (spreadCount <= 0) {
// Guard doesn't dip into the varargs
return guard;
}
final MethodHandle arrayConvertingGuard;
// If the last parameter type of the guard is an array, then it is already itself a guard for a vararg apply
// invocation. We must filter the last argument with toApplyArgs otherwise deeper levels of nesting will fail
// with ClassCastException of NativeArray to Object[].
if (guardType.parameterType(guardParamCount - 1).isArray()) {
arrayConvertingGuard = MH.filterArguments(guard, guardParamCount - 1, NativeFunction.TO_APPLY_ARGS);
} else {
arrayConvertingGuard = guard;
}
return ScriptObject.adaptHandleToVarArgCallSite(arrayConvertingGuard, descParamCount);
}
private static MethodHandle bindImplicitThis(final Object fn, final MethodHandle mh) {
final MethodHandle bound;
if (fn instanceof ScriptFunction && ((ScriptFunction) fn).needsWrappedThis()) {
bound = MH.filterArguments(mh, 1, SCRIPTFUNCTION_GLOBALFILTER);
} else {
bound = mh;
}
return MH.insertArguments(bound, 1, ScriptRuntime.UNDEFINED);
}
/**
* Used for noSuchMethod/noSuchProperty and JSAdapter hooks.
*
* These don't want a callee parameter, so bind that. Name binding is
* optional.
*/
MethodHandle getCallMethodHandle(final MethodType type, final String bindName) {
return pairArguments(bindToNameIfNeeded(bindToCalleeIfNeeded(data.getGenericInvoker(scope)), bindName), type);
}
private static MethodHandle bindToNameIfNeeded(final MethodHandle methodHandle, final String bindName) {
if (bindName == null) {
return methodHandle;
}
// if it is vararg method, we need to extend argument array with
// a new zeroth element that is set to bindName value.
final MethodType methodType = methodHandle.type();
final int parameterCount = methodType.parameterCount();
final boolean isVarArg = parameterCount > 0 && methodType.parameterType(parameterCount - 1).isArray();
if (isVarArg) {
return MH.filterArguments(methodHandle, 1, MH.insertArguments(ADD_ZEROTH_ELEMENT, 1, bindName));
}
return MH.insertArguments(methodHandle, 1, bindName);
}
/**
* Get the guard that checks if a {@link ScriptFunction} is equal to a known
* ScriptFunction, using reference comparison
*
* @param function The ScriptFunction to check against. This will be bound
* to the guard method handle
*
* @return method handle for guard
*/
private static MethodHandle getFunctionGuard(final ScriptFunction function, final int flags) {
assert function.data != null;
// Built-in functions have a 1-1 correspondence to their ScriptFunctionData, so we can use a cheaper identity
// comparison for them.
if (function.data.isBuiltin()) {
return Guards.getIdentityGuard(function);
}
return MH.insertArguments(IS_FUNCTION_MH, 1, function.data);
}
/**
* Get a guard that checks if a {@link ScriptFunction} is equal to a known
* ScriptFunction using reference comparison, and whether the type of the
* second argument (this-object) is not a JavaScript primitive type.
*
* @param function The ScriptFunction to check against. This will be bound
* to the guard method handle
*
* @return method handle for guard
*/
private static MethodHandle getNonStrictFunctionGuard(final ScriptFunction function) {
assert function.data != null;
return MH.insertArguments(IS_NONSTRICT_FUNCTION, 2, function.data);
}
@SuppressWarnings("unused")
private static boolean isFunctionMH(final Object self, final ScriptFunctionData data) {
return self instanceof ScriptFunction && ((ScriptFunction) self).data == data;
}
@SuppressWarnings("unused")
private static boolean isNonStrictFunction(final Object self, final Object arg, final ScriptFunctionData data) {
return self instanceof ScriptFunction && ((ScriptFunction) self).data == data && arg instanceof ScriptObject;
}
//TODO this can probably be removed given that we have builtin switchpoints in the context
@SuppressWarnings("unused")
private static boolean isApplyFunction(final boolean appliedFnCondition, final Object self, final Object expectedSelf) {
// NOTE: we're using self == expectedSelf as we're only using this with built-in functions apply() and call()
return appliedFnCondition && self == expectedSelf;
}
@SuppressWarnings("unused")
private static Object[] addZerothElement(final Object[] args, final Object value) {
// extends input array with by adding new zeroth element
final Object[] src = args == null ? ScriptRuntime.EMPTY_ARRAY : args;
final Object[] result = new Object[src.length + 1];
System.arraycopy(src, 0, result, 1, src.length);
result[0] = value;
return result;
}
@SuppressWarnings("unused")
private static Object invokeSync(final ScriptFunction func, final Object sync, final Object self, final Object... args)
throws Throwable {
final Object syncObj = sync == UNDEFINED ? self : sync;
synchronized (syncObj) {
return func.invoke(self, args);
}
}
private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) {
return MH.findStatic(MethodHandles.lookup(), ScriptFunction.class, name, MH.type(rtype, types));
}
private static MethodHandle findOwnMH_V(final String name, final Class<?> rtype, final Class<?>... types) {
return MH.findVirtual(MethodHandles.lookup(), ScriptFunction.class, name, MH.type(rtype, types));
}
}