blob: c95481bc22c99d6bce210244d8f1c7456770ff66 [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.linker;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.CompositeOperation;
import jdk.internal.dynalink.NamedOperation;
import jdk.internal.dynalink.Operation;
import jdk.internal.dynalink.StandardOperation;
import jdk.internal.dynalink.support.NameCodec;
import jdk.nashorn.internal.ir.debug.NashornTextifier;
import jdk.nashorn.internal.runtime.AccessControlContextFactory;
import jdk.nashorn.internal.runtime.ScriptRuntime;
/**
* Nashorn-specific implementation of Dynalink's {@link CallSiteDescriptor}.
* The reason we have our own subclass is that we're storing flags in an
* additional primitive field. The class also exposes some useful utilities in
* form of static methods.
*/
public final class NashornCallSiteDescriptor extends CallSiteDescriptor {
// Lowest three bits describe the operation
/** Property getter operation {@code obj.prop} */
public static final int GET_PROPERTY = 0;
/** Element getter operation {@code obj[index]} */
public static final int GET_ELEMENT = 1;
/** Property getter operation, subsequently invoked {@code obj.prop()} */
public static final int GET_METHOD_PROPERTY = 2;
/** Element getter operation, subsequently invoked {@code obj[index]()} */
public static final int GET_METHOD_ELEMENT = 3;
/** Property setter operation {@code obj.prop = value} */
public static final int SET_PROPERTY = 4;
/** Element setter operation {@code obj[index] = value} */
public static final int SET_ELEMENT = 5;
/** Call operation {@code fn(args...)} */
public static final int CALL = 6;
/** New operation {@code new Constructor(args...)} */
public static final int NEW = 7;
private static final int OPERATION_MASK = 7;
// Correspond to the operation indices above.
private static final Operation[] OPERATIONS = new Operation[] {
new CompositeOperation(StandardOperation.GET_PROPERTY, StandardOperation.GET_ELEMENT, StandardOperation.GET_METHOD),
new CompositeOperation(StandardOperation.GET_ELEMENT, StandardOperation.GET_PROPERTY, StandardOperation.GET_METHOD),
new CompositeOperation(StandardOperation.GET_METHOD, StandardOperation.GET_PROPERTY, StandardOperation.GET_ELEMENT),
new CompositeOperation(StandardOperation.GET_METHOD, StandardOperation.GET_ELEMENT, StandardOperation.GET_PROPERTY),
new CompositeOperation(StandardOperation.SET_PROPERTY, StandardOperation.SET_ELEMENT),
new CompositeOperation(StandardOperation.SET_ELEMENT, StandardOperation.SET_PROPERTY),
StandardOperation.CALL,
StandardOperation.NEW
};
/** Flags that the call site references a scope variable (it's an identifier reference or a var declaration, not a
* property access expression. */
public static final int CALLSITE_SCOPE = 1 << 3;
/** Flags that the call site is in code that uses ECMAScript strict mode. */
public static final int CALLSITE_STRICT = 1 << 4;
/** Flags that a property getter or setter call site references a scope variable that is located at a known distance
* in the scope chain. Such getters and setters can often be linked more optimally using these assumptions. */
public static final int CALLSITE_FAST_SCOPE = 1 << 5;
/** Flags that a callsite type is optimistic, i.e. we might get back a wider return value than encoded in the
* descriptor, and in that case we have to throw an UnwarrantedOptimismException */
public static final int CALLSITE_OPTIMISTIC = 1 << 6;
/** Is this really an apply that we try to call as a call? */
public static final int CALLSITE_APPLY_TO_CALL = 1 << 7;
/** Does this a callsite for a variable declaration? */
public static final int CALLSITE_DECLARE = 1 << 8;
/** Flags that the call site is profiled; Contexts that have {@code "profile.callsites"} boolean property set emit
* code where call sites have this flag set. */
public static final int CALLSITE_PROFILE = 1 << 9;
/** Flags that the call site is traced; Contexts that have {@code "trace.callsites"} property set emit code where
* call sites have this flag set. */
public static final int CALLSITE_TRACE = 1 << 10;
/** Flags that the call site linkage miss (and thus, relinking) is traced; Contexts that have the keyword
* {@code "miss"} in their {@code "trace.callsites"} property emit code where call sites have this flag set. */
public static final int CALLSITE_TRACE_MISSES = 1 << 11;
/** Flags that entry/exit to/from the method linked at call site are traced; Contexts that have the keyword
* {@code "enterexit"} in their {@code "trace.callsites"} property emit code where call sites have this flag set. */
public static final int CALLSITE_TRACE_ENTEREXIT = 1 << 12;
/** Flags that values passed as arguments to and returned from the method linked at call site are traced; Contexts
* that have the keyword {@code "values"} in their {@code "trace.callsites"} property emit code where call sites
* have this flag set. */
public static final int CALLSITE_TRACE_VALUES = 1 << 13;
//we could have more tracing flags here, for example CALLSITE_TRACE_SCOPE, but bits are a bit precious
//right now given the program points
/**
* Number of bits the program point is shifted to the left in the flags (lowest bit containing a program point).
* Always one larger than the largest flag shift. Note that introducing a new flag halves the number of program
* points we can have.
* TODO: rethink if we need the various profile/trace flags or the linker can use the Context instead to query its
* trace/profile settings.
*/
public static final int CALLSITE_PROGRAM_POINT_SHIFT = 14;
/**
* Maximum program point value. We have 18 bits left over after flags, and
* it should be plenty. Program points are local to a single function. Every
* function maps to a single JVM bytecode method that can have at most 65535
* bytes. (Large functions are synthetically split into smaller functions.)
* A single invokedynamic is 5 bytes; even if a method consists of only
* invokedynamic instructions that leaves us with at most 65535/5 = 13107
* program points for the largest single method; those can be expressed on
* 14 bits. It is true that numbering of program points is independent of
* bytecode representation, but if a function would need more than ~14 bits
* for the program points, then it is reasonable to presume splitter
* would've split it into several smaller functions already.
*/
public static final int MAX_PROGRAM_POINT_VALUE = (1 << 32 - CALLSITE_PROGRAM_POINT_SHIFT) - 1;
/**
* Flag mask to get the program point flags
*/
public static final int FLAGS_MASK = (1 << CALLSITE_PROGRAM_POINT_SHIFT) - 1;
private static final ClassValue<ConcurrentMap<NashornCallSiteDescriptor, NashornCallSiteDescriptor>> canonicals =
new ClassValue<ConcurrentMap<NashornCallSiteDescriptor,NashornCallSiteDescriptor>>() {
@Override
protected ConcurrentMap<NashornCallSiteDescriptor, NashornCallSiteDescriptor> computeValue(final Class<?> type) {
return new ConcurrentHashMap<>();
}
};
private static final AccessControlContext GET_LOOKUP_PERMISSION_CONTEXT =
AccessControlContextFactory.createAccessControlContext(CallSiteDescriptor.GET_LOOKUP_PERMISSION_NAME);
private final int flags;
/**
* Function used by {@link NashornTextifier} to represent call site flags in
* human readable form
* @param flags call site flags
* @return human readable form of this callsite descriptor
*/
public static String toString(final int flags) {
final StringBuilder sb = new StringBuilder();
if ((flags & CALLSITE_SCOPE) != 0) {
if ((flags & CALLSITE_FAST_SCOPE) != 0) {
sb.append("fastscope ");
} else {
assert (flags & CALLSITE_FAST_SCOPE) == 0 : "can't be fastscope without scope";
sb.append("scope ");
}
if ((flags & CALLSITE_DECLARE) != 0) {
sb.append("declare ");
}
}
if ((flags & CALLSITE_APPLY_TO_CALL) != 0) {
sb.append("apply2call ");
}
if ((flags & CALLSITE_STRICT) != 0) {
sb.append("strict ");
}
return sb.length() == 0 ? "" : " " + sb.toString().trim();
}
/**
* Given call site flags, returns the operation name encoded in them.
* @param flags flags
* @return the operation name
*/
public static String getOperationName(final int flags) {
switch(flags & OPERATION_MASK) {
case 0: return "GET_PROPERTY";
case 1: return "GET_ELEMENT";
case 2: return "GET_METHOD_PROPERTY";
case 3: return "GET_METHOD_ELEMENT";
case 4: return "SET_PROPERTY";
case 5: return "SET_ELEMENT";
case 6: return "CALL";
case 7: return "NEW";
default: throw new AssertionError();
}
}
/**
* Retrieves a Nashorn call site descriptor with the specified values. Since call site descriptors are immutable
* this method is at liberty to retrieve canonicalized instances (although it is not guaranteed it will do so).
* @param lookup the lookup describing the script
* @param name the name at the call site. Can not be null, but it can be empty.
* @param methodType the method type at the call site
* @param flags Nashorn-specific call site flags
* @return a call site descriptor with the specified values.
*/
public static NashornCallSiteDescriptor get(final MethodHandles.Lookup lookup, final String name,
final MethodType methodType, final int flags) {
final Operation baseOp = OPERATIONS[flags & OPERATION_MASK];
final String decodedName = NameCodec.decode(name);
// TODO: introduce caching of NamedOperation
final Operation op = decodedName.isEmpty() ? baseOp : new NamedOperation(baseOp, decodedName);
return get(lookup, op, methodType, flags);
}
private static NashornCallSiteDescriptor get(final MethodHandles.Lookup lookup, final Operation operation, final MethodType methodType, final int flags) {
final NashornCallSiteDescriptor csd = new NashornCallSiteDescriptor(lookup, operation, methodType, flags);
// Many of these call site descriptors are identical (e.g. every getter for a property color will be
// "GET_PROPERTY:color(Object)Object", so it makes sense canonicalizing them.
final ConcurrentMap<NashornCallSiteDescriptor, NashornCallSiteDescriptor> classCanonicals = canonicals.get(lookup.lookupClass());
final NashornCallSiteDescriptor canonical = classCanonicals.putIfAbsent(csd, csd);
return canonical != null ? canonical : csd;
}
private NashornCallSiteDescriptor(final MethodHandles.Lookup lookup, final Operation operation, final MethodType methodType, final int flags) {
super(lookup, operation, methodType);
this.flags = flags;
}
static Lookup getLookupInternal(final CallSiteDescriptor csd) {
if (csd instanceof NashornCallSiteDescriptor) {
return ((NashornCallSiteDescriptor)csd).getLookupPrivileged();
}
return AccessController.doPrivileged((PrivilegedAction<Lookup>)()->csd.getLookup(), GET_LOOKUP_PERMISSION_CONTEXT);
}
@Override
public boolean equals(final Object obj) {
return super.equals(obj) && flags == ((NashornCallSiteDescriptor)obj).flags;
}
@Override
public int hashCode() {
return super.hashCode() ^ flags;
}
/**
* Returns the named operand in this descriptor's operation. Equivalent to
* {@code ((NamedOperation)getOperation()).getName().toString()} for call
* sites with a named operand. For call sites without named operands returns null.
* @return the named operand in this descriptor's operation.
*/
public String getOperand() {
return getOperand(this);
}
/**
* Returns the named operand in the passed descriptor's operation.
* Equivalent to
* {@code ((NamedOperation)desc.getOperation()).getName().toString()} for
* descriptors with a named operand. For descriptors without named operands
* returns null.
* @param desc the call site descriptors
* @return the named operand in this descriptor's operation.
*/
public static String getOperand(final CallSiteDescriptor desc) {
final Operation operation = desc.getOperation();
return operation instanceof NamedOperation ? ((NamedOperation)operation).getName().toString() : null;
}
/**
* Returns the first operation in this call site descriptor's potentially
* composite operation. E.g. if this call site descriptor has a composite
* operation {@code GET_PROPERTY|GET_METHOD|GET_ELEM}, it will return
* {@code GET_PROPERTY}. Nashorn - being a ECMAScript engine - does not
* distinguish between property, element, and method namespace; ECMAScript
* objects just have one single property namespace for all these, therefore
* it is largely irrelevant what the composite operation is structured like;
* if the first operation can't be satisfied, neither can the others. The
* first operation is however sometimes used to slightly alter the
* semantics; for example, a distinction between {@code GET_PROPERTY} and
* {@code GET_METHOD} being the first operation can translate into whether
* {@code "__noSuchProperty__"} or {@code "__noSuchMethod__"} will be
* executed in case the property is not found. Note that if a call site
* descriptor comes from outside of Nashorn, its class will be different,
* and there is no guarantee about the way it composes its operations. For
* that reason, for potentially foreign call site descriptors you should use
* {@link #getFirstStandardOperation(CallSiteDescriptor)} instead.
* @return the first operation in this call site descriptor. Note this will
* always be a {@code StandardOperation} as Nashorn internally only uses
* standard operations.
*/
public StandardOperation getFirstOperation() {
final Operation base = NamedOperation.getBaseOperation(getOperation());
if (base instanceof CompositeOperation) {
return (StandardOperation)((CompositeOperation)base).getOperation(0);
}
return (StandardOperation)base;
}
/**
* Returns the first standard operation in the (potentially composite)
* operation of the passed call site descriptor.
* @param desc the call site descriptor.
* @return Returns the first standard operation in the (potentially
* composite) operation of the passed call site descriptor. Can return null
* if the call site contains no standard operations.
*/
public static StandardOperation getFirstStandardOperation(final CallSiteDescriptor desc) {
final Operation base = NamedOperation.getBaseOperation(desc.getOperation());
if (base instanceof StandardOperation) {
return (StandardOperation)base;
} else if (base instanceof CompositeOperation) {
final CompositeOperation cop = (CompositeOperation)base;
for(int i = 0; i < cop.getOperationCount(); ++i) {
final Operation op = cop.getOperation(i);
if (op instanceof StandardOperation) {
return (StandardOperation)op;
}
}
}
return null;
}
/**
* Returns true if the passed call site descriptor's operation contains (or
* is) the specified standard operation.
* @param desc the call site descriptor.
* @param operation the operation whose presence is tested.
* @return Returns true if the call site descriptor's operation contains (or
* is) the specified standard operation.
*/
public static boolean contains(final CallSiteDescriptor desc, final StandardOperation operation) {
return CompositeOperation.contains(NamedOperation.getBaseOperation(desc.getOperation()), operation);
}
/**
* Returns the error message to be used when CALL or NEW is used on a non-function.
*
* @param obj object on which CALL or NEW is used
* @return error message
*/
public String getFunctionErrorMessage(final Object obj) {
final String funcDesc = getOperand();
return funcDesc != null? funcDesc : ScriptRuntime.safeToString(obj);
}
/**
* Returns the error message to be used when CALL or NEW is used on a non-function.
*
* @param desc call site descriptor
* @param obj object on which CALL or NEW is used
* @return error message
*/
public static String getFunctionErrorMessage(final CallSiteDescriptor desc, final Object obj) {
return desc instanceof NashornCallSiteDescriptor ?
((NashornCallSiteDescriptor)desc).getFunctionErrorMessage(obj) :
ScriptRuntime.safeToString(obj);
}
/**
* Returns the Nashorn-specific flags for this call site descriptor.
* @param desc the descriptor. It can be any kind of a call site descriptor, not necessarily a
* {@code NashornCallSiteDescriptor}. This allows for graceful interoperability when linking Nashorn with code
* generated outside of Nashorn.
* @return the Nashorn-specific flags for the call site, or 0 if the passed descriptor is not a Nashorn call site
* descriptor.
*/
public static int getFlags(final CallSiteDescriptor desc) {
return desc instanceof NashornCallSiteDescriptor ? ((NashornCallSiteDescriptor)desc).flags : 0;
}
/**
* Returns true if this descriptor has the specified flag set, see {@code CALLSITE_*} constants in this class.
* @param flag the tested flag
* @return true if the flag is set, false otherwise
*/
private boolean isFlag(final int flag) {
return (flags & flag) != 0;
}
/**
* Returns true if this descriptor has the specified flag set, see {@code CALLSITE_*} constants in this class.
* @param desc the descriptor. It can be any kind of a call site descriptor, not necessarily a
* {@code NashornCallSiteDescriptor}. This allows for graceful interoperability when linking Nashorn with code
* generated outside of Nashorn.
* @param flag the tested flag
* @return true if the flag is set, false otherwise (it will be false if the descriptor is not a Nashorn call site
* descriptor).
*/
private static boolean isFlag(final CallSiteDescriptor desc, final int flag) {
return (getFlags(desc) & flag) != 0;
}
/**
* Returns true if this descriptor is a Nashorn call site descriptor and has the {@link #CALLSITE_SCOPE} flag set.
* @param desc the descriptor. It can be any kind of a call site descriptor, not necessarily a
* {@code NashornCallSiteDescriptor}. This allows for graceful interoperability when linking Nashorn with code
* generated outside of Nashorn.
* @return true if the descriptor is a Nashorn call site descriptor, and the flag is set, false otherwise.
*/
public static boolean isScope(final CallSiteDescriptor desc) {
return isFlag(desc, CALLSITE_SCOPE);
}
/**
* Returns true if this descriptor is a Nashorn call site descriptor and has the {@link #CALLSITE_FAST_SCOPE} flag set.
* @param desc the descriptor. It can be any kind of a call site descriptor, not necessarily a
* {@code NashornCallSiteDescriptor}. This allows for graceful interoperability when linking Nashorn with code
* generated outside of Nashorn.
* @return true if the descriptor is a Nashorn call site descriptor, and the flag is set, false otherwise.
*/
public static boolean isFastScope(final CallSiteDescriptor desc) {
return isFlag(desc, CALLSITE_FAST_SCOPE);
}
/**
* Returns true if this descriptor is a Nashorn call site descriptor and has the {@link #CALLSITE_STRICT} flag set.
* @param desc the descriptor. It can be any kind of a call site descriptor, not necessarily a
* {@code NashornCallSiteDescriptor}. This allows for graceful interoperability when linking Nashorn with code
* generated outside of Nashorn.
* @return true if the descriptor is a Nashorn call site descriptor, and the flag is set, false otherwise.
*/
public static boolean isStrict(final CallSiteDescriptor desc) {
return isFlag(desc, CALLSITE_STRICT);
}
/**
* Returns true if this is an apply call that we try to call as
* a "call"
* @param desc descriptor
* @return true if apply to call
*/
public static boolean isApplyToCall(final CallSiteDescriptor desc) {
return isFlag(desc, CALLSITE_APPLY_TO_CALL);
}
/**
* Is this an optimistic call site
* @param desc descriptor
* @return true if optimistic
*/
public static boolean isOptimistic(final CallSiteDescriptor desc) {
return isFlag(desc, CALLSITE_OPTIMISTIC);
}
/**
* Does this callsite contain a declaration for its target?
* @param desc descriptor
* @return true if contains declaration
*/
public static boolean isDeclaration(final CallSiteDescriptor desc) {
return isFlag(desc, CALLSITE_DECLARE);
}
/**
* Returns true if {@code flags} has the {@link #CALLSITE_STRICT} bit set.
* @param flags the flags
* @return true if the flag is set, false otherwise.
*/
public static boolean isStrictFlag(final int flags) {
return (flags & CALLSITE_STRICT) != 0;
}
/**
* Returns true if {@code flags} has the {@link #CALLSITE_SCOPE} bit set.
* @param flags the flags
* @return true if the flag is set, false otherwise.
*/
public static boolean isScopeFlag(final int flags) {
return (flags & CALLSITE_SCOPE) != 0;
}
/**
* Get a program point from a descriptor (must be optimistic)
* @param desc descriptor
* @return program point
*/
public static int getProgramPoint(final CallSiteDescriptor desc) {
assert isOptimistic(desc) : "program point requested from non-optimistic descriptor " + desc;
return getFlags(desc) >> CALLSITE_PROGRAM_POINT_SHIFT;
}
boolean isProfile() {
return isFlag(CALLSITE_PROFILE);
}
boolean isTrace() {
return isFlag(CALLSITE_TRACE);
}
boolean isTraceMisses() {
return isFlag(CALLSITE_TRACE_MISSES);
}
boolean isTraceEnterExit() {
return isFlag(CALLSITE_TRACE_ENTEREXIT);
}
boolean isTraceObjects() {
return isFlag(CALLSITE_TRACE_VALUES);
}
boolean isOptimistic() {
return isFlag(CALLSITE_OPTIMISTIC);
}
@Override
public CallSiteDescriptor changeMethodTypeInternal(final MethodType newMethodType) {
return get(getLookupPrivileged(), getOperation(), newMethodType, flags);
}
}