| /* |
| * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.runtime; |
| |
| import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall; |
| import static jdk.nashorn.internal.lookup.Lookup.MH; |
| import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; |
| import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.getProgramPoint; |
| import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote; |
| |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.SwitchPoint; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.logging.Level; |
| import jdk.internal.dynalink.CallSiteDescriptor; |
| import jdk.internal.dynalink.DynamicLinker; |
| import jdk.internal.dynalink.linker.GuardedInvocation; |
| import jdk.internal.dynalink.linker.LinkRequest; |
| import jdk.nashorn.internal.lookup.Lookup; |
| import jdk.nashorn.internal.lookup.MethodHandleFactory; |
| import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; |
| import jdk.nashorn.internal.runtime.logging.DebugLogger; |
| import jdk.nashorn.internal.runtime.logging.Loggable; |
| import jdk.nashorn.internal.runtime.logging.Logger; |
| |
| /** |
| * Each context owns one of these. This is basically table of accessors |
| * for global properties. A global constant is evaluated to a MethodHandle.constant |
| * for faster access and to avoid walking to proto chain looking for it. |
| * |
| * We put a switchpoint on the global setter, which invalidates the |
| * method handle constant getters, and reverts to the standard access strategy |
| * |
| * However, there is a twist - while certain globals like "undefined" and "Math" |
| * are usually never reassigned, a global value can be reset once, and never again. |
| * This is a rather common pattern, like: |
| * |
| * x = function(something) { ... |
| * |
| * Thus everything registered as a global constant gets an extra chance. Set once, |
| * reregister the switchpoint. Set twice or more - don't try again forever, or we'd |
| * just end up relinking our way into megamorphism. |
| * |
| * Also it has to be noted that this kind of linking creates a coupling between a Global |
| * and the call sites in compiled code belonging to the Context. For this reason, the |
| * linkage becomes incorrect as soon as the Context has more than one Global. The |
| * {@link #invalidateForever()} is invoked by the Context to invalidate all linkages and |
| * turn off the functionality of this object as soon as the Context's {@link Context#newGlobal()} is invoked |
| * for second time. |
| * |
| * We can extend this to ScriptObjects in general (GLOBAL_ONLY=false), which requires |
| * a receiver guard on the constant getter, but it currently leaks memory and its benefits |
| * have not yet been investigated property. |
| * |
| * As long as all Globals in a Context share the same GlobalConstants instance, we need synchronization |
| * whenever we access it. |
| */ |
| @Logger(name="const") |
| public final class GlobalConstants implements Loggable { |
| |
| /** |
| * Should we only try to link globals as constants, and not generic script objects. |
| * Script objects require a receiver guard, which is memory intensive, so this is currently |
| * disabled. We might implement a weak reference based approach to this later. |
| */ |
| public static final boolean GLOBAL_ONLY = true; |
| |
| private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); |
| |
| private static final MethodHandle INVALIDATE_SP = virtualCall(LOOKUP, GlobalConstants.class, "invalidateSwitchPoint", Object.class, Object.class, Access.class).methodHandle(); |
| private static final MethodHandle RECEIVER_GUARD = staticCall(LOOKUP, GlobalConstants.class, "receiverGuard", boolean.class, Access.class, Object.class, Object.class).methodHandle(); |
| |
| /** Logger for constant getters */ |
| private final DebugLogger log; |
| |
| /** |
| * Access map for this global - associates a symbol name with an Access object, with getter |
| * and invalidation information |
| */ |
| private final Map<String, Access> map = new HashMap<>(); |
| |
| private final AtomicBoolean invalidatedForever = new AtomicBoolean(false); |
| |
| /** |
| * Constructor - used only by global |
| * @param log logger, or null if none |
| */ |
| public GlobalConstants(final DebugLogger log) { |
| this.log = log == null ? DebugLogger.DISABLED_LOGGER : log; |
| } |
| |
| @Override |
| public DebugLogger getLogger() { |
| return log; |
| } |
| |
| @Override |
| public DebugLogger initLogger(final Context context) { |
| return DebugLogger.DISABLED_LOGGER; |
| } |
| |
| /** |
| * Information about a constant access and its potential invalidations |
| */ |
| private static class Access { |
| /** name of symbol */ |
| private final String name; |
| |
| /** switchpoint that invalidates the getters and setters for this access */ |
| private SwitchPoint sp; |
| |
| /** invalidation count for this access, i.e. how many times has this property been reset */ |
| private int invalidations; |
| |
| /** has a guard guarding this property getter failed? */ |
| private boolean guardFailed; |
| |
| private static final int MAX_RETRIES = 2; |
| |
| private Access(final String name, final SwitchPoint sp) { |
| this.name = name; |
| this.sp = sp; |
| } |
| |
| private boolean hasBeenInvalidated() { |
| return sp.hasBeenInvalidated(); |
| } |
| |
| private boolean guardFailed() { |
| return guardFailed; |
| } |
| |
| private void failGuard() { |
| invalidateOnce(); |
| guardFailed = true; |
| } |
| |
| private void newSwitchPoint() { |
| assert hasBeenInvalidated(); |
| sp = new SwitchPoint(); |
| } |
| |
| private void invalidate(final int count) { |
| if (!sp.hasBeenInvalidated()) { |
| SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); |
| invalidations += count; |
| } |
| } |
| |
| /** |
| * Invalidate the access, but do not contribute to the invalidation count |
| */ |
| private void invalidateUncounted() { |
| invalidate(0); |
| } |
| |
| /** |
| * Invalidate the access, and contribute 1 to the invalidation count |
| */ |
| private void invalidateOnce() { |
| invalidate(1); |
| } |
| |
| /** |
| * Invalidate the access and make sure that we never try to turn this into |
| * a MethodHandle.constant getter again |
| */ |
| private void invalidateForever() { |
| invalidate(MAX_RETRIES); |
| } |
| |
| /** |
| * Are we allowed to relink this as constant getter, even though it |
| * it has been reset |
| * @return true if we can relink as constant, one retry is allowed |
| */ |
| private boolean mayRetry() { |
| return invalidations < MAX_RETRIES; |
| } |
| |
| @Override |
| public String toString() { |
| return "[" + quote(name) + " <id=" + Debug.id(this) + "> inv#=" + invalidations + '/' + MAX_RETRIES + " sp_inv=" + sp.hasBeenInvalidated() + ']'; |
| } |
| |
| String getName() { |
| return name; |
| } |
| |
| SwitchPoint getSwitchPoint() { |
| return sp; |
| } |
| } |
| |
| /** |
| * To avoid an expensive global guard "is this the same global", similar to the |
| * receiver guard on the ScriptObject level, we invalidate all getters once |
| * when we switch globals. This is used from the class cache. We _can_ reuse |
| * the same class for a new global, but the builtins and global scoped variables |
| * will have changed. |
| */ |
| public void invalidateAll() { |
| if (!invalidatedForever.get()) { |
| log.info("New global created - invalidating all constant callsites without increasing invocation count."); |
| synchronized (this) { |
| for (final Access acc : map.values()) { |
| acc.invalidateUncounted(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * To avoid an expensive global guard "is this the same global", similar to the |
| * receiver guard on the ScriptObject level, we invalidate all getters when the |
| * second Global is created by the Context owning this instance. After this |
| * method is invoked, this GlobalConstants instance will both invalidate all the |
| * switch points it produced, and it will stop handing out new method handles |
| * altogether. |
| */ |
| public void invalidateForever() { |
| if (invalidatedForever.compareAndSet(false, true)) { |
| log.info("New global created - invalidating all constant callsites."); |
| synchronized (this) { |
| for (final Access acc : map.values()) { |
| acc.invalidateForever(); |
| } |
| map.clear(); |
| } |
| } |
| } |
| |
| /** |
| * Invalidate the switchpoint of an access - we have written to |
| * the property |
| * |
| * @param obj receiver |
| * @param acc access |
| * |
| * @return receiver, so this can be used as param filter |
| */ |
| @SuppressWarnings("unused") |
| private synchronized Object invalidateSwitchPoint(final Object obj, final Access acc) { |
| if (log.isEnabled()) { |
| log.info("*** Invalidating switchpoint " + acc.getSwitchPoint() + " for receiver=" + obj + " access=" + acc); |
| } |
| acc.invalidateOnce(); |
| if (acc.mayRetry()) { |
| if (log.isEnabled()) { |
| log.info("Retry is allowed for " + acc + "... Creating a new switchpoint."); |
| } |
| acc.newSwitchPoint(); |
| } else { |
| if (log.isEnabled()) { |
| log.info("This was the last time I allowed " + quote(acc.getName()) + " to relink as constant."); |
| } |
| } |
| return obj; |
| } |
| |
| private Access getOrCreateSwitchPoint(final String name) { |
| Access acc = map.get(name); |
| if (acc != null) { |
| return acc; |
| } |
| final SwitchPoint sp = new SwitchPoint(); |
| map.put(name, acc = new Access(name, sp)); |
| return acc; |
| } |
| |
| /** |
| * Called from script object on property deletion to erase a property |
| * that might be linked as MethodHandle.constant and force relink |
| * @param name name of property |
| */ |
| void delete(final String name) { |
| if (!invalidatedForever.get()) { |
| synchronized (this) { |
| final Access acc = map.get(name); |
| if (acc != null) { |
| acc.invalidateForever(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Receiver guard is used if we extend the global constants to script objects in general. |
| * As the property can have different values in different script objects, while Global is |
| * by definition a singleton, we need this for ScriptObject constants (currently disabled) |
| * |
| * TODO: Note - this seems to cause memory leaks. Use weak references? But what is leaking seems |
| * to be the Access objects, which isn't the case for Globals. Weird. |
| * |
| * @param acc access |
| * @param boundReceiver the receiver bound to the callsite |
| * @param receiver the receiver to check against |
| * |
| * @return true if this receiver is still the one we bound to the callsite |
| */ |
| @SuppressWarnings("unused") |
| private static boolean receiverGuard(final Access acc, final Object boundReceiver, final Object receiver) { |
| final boolean id = receiver == boundReceiver; |
| if (!id) { |
| acc.failGuard(); |
| } |
| return id; |
| } |
| |
| private static boolean isGlobalSetter(final ScriptObject receiver, final FindProperty find) { |
| if (find == null) { |
| return receiver.isScope(); |
| } |
| return find.getOwner().isGlobal(); |
| } |
| |
| /** |
| * Augment a setter with switchpoint for invalidating its getters, should the setter be called |
| * |
| * @param find property lookup |
| * @param inv normal guarded invocation for this setter, as computed by the ScriptObject linker |
| * @param desc callsite descriptor |
| * @param request link request |
| * |
| * @return null if failed to set up constant linkage |
| */ |
| GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) { |
| if (invalidatedForever.get() || (GLOBAL_ONLY && !isGlobalSetter(receiver, find))) { |
| return null; |
| } |
| |
| final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); |
| |
| synchronized (this) { |
| final Access acc = getOrCreateSwitchPoint(name); |
| |
| if (log.isEnabled()) { |
| log.fine("Trying to link constant SETTER ", acc); |
| } |
| |
| if (!acc.mayRetry() || invalidatedForever.get()) { |
| if (log.isEnabled()) { |
| log.fine("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); |
| } |
| return null; |
| } |
| |
| if (acc.hasBeenInvalidated()) { |
| log.info("New chance for " + acc); |
| acc.newSwitchPoint(); |
| } |
| |
| assert !acc.hasBeenInvalidated(); |
| |
| // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter |
| final MethodHandle target = inv.getInvocation(); |
| final Class<?> receiverType = target.type().parameterType(0); |
| final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP, this); |
| final MethodHandle invalidator = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType)); |
| final MethodHandle mh = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc)); |
| |
| assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints()); |
| log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint()); |
| return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException()); |
| } |
| } |
| |
| /** |
| * Try to reuse constant method handles for getters |
| * @param c constant value |
| * @return method handle (with dummy receiver) that returns this constant |
| */ |
| public static MethodHandle staticConstantGetter(final Object c) { |
| return MH.dropArguments(JSType.unboxConstant(c), 0, Object.class); |
| } |
| |
| private MethodHandle constantGetter(final Object c) { |
| final MethodHandle mh = staticConstantGetter(c); |
| if (log.isEnabled()) { |
| return MethodHandleFactory.addDebugPrintout(log, Level.FINEST, mh, "getting as constant"); |
| } |
| return mh; |
| } |
| |
| /** |
| * Try to turn a getter into a MethodHandle.constant, if possible |
| * |
| * @param find property lookup |
| * @param receiver receiver |
| * @param desc callsite descriptor |
| * |
| * @return resulting getter, or null if failed to create constant |
| */ |
| GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) { |
| // Only use constant getter for fast scope access, because the receiver may change between invocations |
| // for slow-scope and non-scope callsites. |
| // Also return null for user accessor properties as they may have side effects. |
| if (invalidatedForever.get() || !NashornCallSiteDescriptor.isFastScope(desc) |
| || (GLOBAL_ONLY && !find.getOwner().isGlobal()) |
| || find.getProperty() instanceof UserAccessorProperty) { |
| return null; |
| } |
| |
| final boolean isOptimistic = NashornCallSiteDescriptor.isOptimistic(desc); |
| final int programPoint = isOptimistic ? getProgramPoint(desc) : INVALID_PROGRAM_POINT; |
| final Class<?> retType = desc.getMethodType().returnType(); |
| final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); |
| |
| synchronized (this) { |
| final Access acc = getOrCreateSwitchPoint(name); |
| |
| log.fine("Starting to look up object value " + name); |
| final Object c = find.getObjectValue(); |
| |
| if (log.isEnabled()) { |
| log.fine("Trying to link constant GETTER " + acc + " value = " + c); |
| } |
| |
| if (acc.hasBeenInvalidated() || acc.guardFailed() || invalidatedForever.get()) { |
| if (log.isEnabled()) { |
| log.info("*** GET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); |
| } |
| return null; |
| } |
| |
| final MethodHandle cmh = constantGetter(c); |
| |
| MethodHandle mh; |
| MethodHandle guard; |
| |
| if (isOptimistic) { |
| if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) { |
| //widen return type - this is pessimistic, so it will always work |
| mh = MH.asType(cmh, cmh.type().changeReturnType(retType)); |
| } else { |
| //immediately invalidate - we asked for a too wide constant as a narrower one |
| mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class); |
| } |
| } else { |
| //pessimistic return type filter |
| mh = Lookup.filterReturnType(cmh, retType); |
| } |
| |
| if (find.getOwner().isGlobal()) { |
| guard = null; |
| } else { |
| guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver); |
| } |
| |
| if (log.isEnabled()) { |
| log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint()); |
| mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc); |
| } |
| |
| return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null); |
| } |
| } |
| } |