--- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/GlobalConstants.java 2020-04-15 18:49:51.000000000 +0530 +++ /dev/null 2020-04-15 18:49:51.000000000 +0530 @@ -1,486 +0,0 @@ -/* - * 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.dynalink.CallSiteDescriptor; -import jdk.dynalink.DynamicLinker; -import jdk.dynalink.linker.GuardedInvocation; -import jdk.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 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) + " 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 Object 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 = NashornCallSiteDescriptor.getOperand(desc); - - 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 = NashornCallSiteDescriptor.getOperand(desc); - - 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); - } - } -}