src/jdk/nashorn/internal/runtime/GlobalConstants.java

changeset 1089
981feb6ad9cc
parent 1054
3c57bcd0c73f
child 1530
fd307cc5f58c
     1.1 --- a/src/jdk/nashorn/internal/runtime/GlobalConstants.java	Thu Nov 06 13:15:52 2014 +0100
     1.2 +++ b/src/jdk/nashorn/internal/runtime/GlobalConstants.java	Thu Nov 06 17:06:56 2014 +0100
     1.3 @@ -31,12 +31,14 @@
     1.4  import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
     1.5  import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.getProgramPoint;
     1.6  import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
     1.7 +
     1.8  import java.lang.invoke.MethodHandle;
     1.9  import java.lang.invoke.MethodHandles;
    1.10  import java.lang.invoke.SwitchPoint;
    1.11  import java.util.Arrays;
    1.12  import java.util.HashMap;
    1.13  import java.util.Map;
    1.14 +import java.util.concurrent.atomic.AtomicBoolean;
    1.15  import java.util.logging.Level;
    1.16  import jdk.internal.dynalink.CallSiteDescriptor;
    1.17  import jdk.internal.dynalink.DynamicLinker;
    1.18 @@ -50,7 +52,7 @@
    1.19  import jdk.nashorn.internal.runtime.logging.Logger;
    1.20  
    1.21  /**
    1.22 - * Each global owns one of these. This is basically table of accessors
    1.23 + * Each context owns one of these. This is basically table of accessors
    1.24   * for global properties. A global constant is evaluated to a MethodHandle.constant
    1.25   * for faster access and to avoid walking to proto chain looking for it.
    1.26   *
    1.27 @@ -67,12 +69,19 @@
    1.28   * reregister the switchpoint. Set twice or more - don't try again forever, or we'd
    1.29   * just end up relinking our way into megamorphisism.
    1.30   *
    1.31 + * Also it has to be noted that this kind of linking creates a coupling between a Global
    1.32 + * and the call sites in compiled code belonging to the Context. For this reason, the
    1.33 + * linkage becomes incorrect as soon as the Context has more than one Global. The
    1.34 + * {@link #invalidateForever()} is invoked by the Context to invalidate all linkages and
    1.35 + * turn off the functionality of this object as soon as the Context's {@link Context#newGlobal()} is invoked
    1.36 + * for second time.
    1.37 + *
    1.38   * We can extend this to ScriptObjects in general (GLOBAL_ONLY=false), which requires
    1.39   * a receiver guard on the constant getter, but it currently leaks memory and its benefits
    1.40   * have not yet been investigated property.
    1.41   *
    1.42 - * As long as all Globals share the same constant instance, we need synchronization
    1.43 - * whenever we access the instance.
    1.44 + * As long as all Globals in a Context share the same GlobalConstants instance, we need synchronization
    1.45 + * whenever we access it.
    1.46   */
    1.47  @Logger(name="const")
    1.48  public final class GlobalConstants implements Loggable {
    1.49 @@ -82,7 +91,7 @@
    1.50       * Script objects require a receiver guard, which is memory intensive, so this is currently
    1.51       * disabled. We might implement a weak reference based approach to this later.
    1.52       */
    1.53 -    private static final boolean GLOBAL_ONLY = true;
    1.54 +    public static final boolean GLOBAL_ONLY = true;
    1.55  
    1.56      private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
    1.57  
    1.58 @@ -98,6 +107,8 @@
    1.59       */
    1.60      private final Map<String, Access> map = new HashMap<>();
    1.61  
    1.62 +    private final AtomicBoolean invalidatedForever = new AtomicBoolean(false);
    1.63 +
    1.64      /**
    1.65       * Constructor - used only by global
    1.66       * @param log logger, or null if none
    1.67 @@ -216,10 +227,34 @@
    1.68       * the same class for a new global, but the builtins and global scoped variables
    1.69       * will have changed.
    1.70       */
    1.71 -    public synchronized void invalidateAll() {
    1.72 -        log.info("New global created - invalidating all constant callsites without increasing invocation count.");
    1.73 -        for (final Access acc : map.values()) {
    1.74 -            acc.invalidateUncounted();
    1.75 +    public void invalidateAll() {
    1.76 +        if (!invalidatedForever.get()) {
    1.77 +            log.info("New global created - invalidating all constant callsites without increasing invocation count.");
    1.78 +            synchronized (this) {
    1.79 +                for (final Access acc : map.values()) {
    1.80 +                    acc.invalidateUncounted();
    1.81 +                }
    1.82 +            }
    1.83 +        }
    1.84 +    }
    1.85 +
    1.86 +    /**
    1.87 +     * To avoid an expensive global guard "is this the same global", similar to the
    1.88 +     * receiver guard on the ScriptObject level, we invalidate all getters when the
    1.89 +     * second Global is created by the Context owning this instance. After this
    1.90 +     * method is invoked, this GlobalConstants instance will both invalidate all the
    1.91 +     * switch points it produced, and it will stop handing out new method handles
    1.92 +     * altogether.
    1.93 +     */
    1.94 +    public void invalidateForever() {
    1.95 +        if (invalidatedForever.compareAndSet(false, true)) {
    1.96 +            log.info("New global created - invalidating all constant callsites.");
    1.97 +            synchronized (this) {
    1.98 +                for (final Access acc : map.values()) {
    1.99 +                    acc.invalidateForever();
   1.100 +                }
   1.101 +                map.clear();
   1.102 +            }
   1.103          }
   1.104      }
   1.105  
   1.106 @@ -251,7 +286,7 @@
   1.107          return obj;
   1.108      }
   1.109  
   1.110 -    private synchronized Access getOrCreateSwitchPoint(final String name) {
   1.111 +    private Access getOrCreateSwitchPoint(final String name) {
   1.112          Access acc = map.get(name);
   1.113          if (acc != null) {
   1.114              return acc;
   1.115 @@ -267,9 +302,13 @@
   1.116       * @param name name of property
   1.117       */
   1.118      void delete(final String name) {
   1.119 -        final Access acc = map.get(name);
   1.120 -        if (acc != null) {
   1.121 -            acc.invalidateForever();
   1.122 +        if (!invalidatedForever.get()) {
   1.123 +            synchronized (this) {
   1.124 +                final Access acc = map.get(name);
   1.125 +                if (acc != null) {
   1.126 +                    acc.invalidateForever();
   1.127 +                }
   1.128 +            }
   1.129          }
   1.130      }
   1.131  
   1.132 @@ -313,45 +352,45 @@
   1.133       *
   1.134       * @return null if failed to set up constant linkage
   1.135       */
   1.136 -    synchronized GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) {
   1.137 -        if (GLOBAL_ONLY && !isGlobalSetter(receiver, find)) {
   1.138 +    GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) {
   1.139 +        if (invalidatedForever.get() || (GLOBAL_ONLY && !isGlobalSetter(receiver, find))) {
   1.140              return null;
   1.141          }
   1.142  
   1.143          final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
   1.144  
   1.145 -        final Access acc  = getOrCreateSwitchPoint(name);
   1.146 +        synchronized (this) {
   1.147 +            final Access acc  = getOrCreateSwitchPoint(name);
   1.148  
   1.149 -        if (log.isEnabled()) {
   1.150 -            log.fine("Trying to link constant SETTER ", acc);
   1.151 +            if (log.isEnabled()) {
   1.152 +                log.fine("Trying to link constant SETTER ", acc);
   1.153 +            }
   1.154 +
   1.155 +            if (!acc.mayRetry() || invalidatedForever.get()) {
   1.156 +                if (log.isEnabled()) {
   1.157 +                    log.fine("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation());
   1.158 +                }
   1.159 +                return null;
   1.160 +            }
   1.161 +
   1.162 +            if (acc.hasBeenInvalidated()) {
   1.163 +                log.info("New chance for " + acc);
   1.164 +                acc.newSwitchPoint();
   1.165 +            }
   1.166 +
   1.167 +            assert !acc.hasBeenInvalidated();
   1.168 +
   1.169 +            // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter
   1.170 +            final MethodHandle target           = inv.getInvocation();
   1.171 +            final Class<?>     receiverType     = target.type().parameterType(0);
   1.172 +            final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP,  this);
   1.173 +            final MethodHandle invalidator      = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType));
   1.174 +            final MethodHandle mh               = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc));
   1.175 +
   1.176 +            assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints());
   1.177 +            log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint());
   1.178 +            return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException());
   1.179          }
   1.180 -
   1.181 -        if (!acc.mayRetry()) {
   1.182 -            if (log.isEnabled()) {
   1.183 -                log.fine("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation());
   1.184 -            }
   1.185 -            return null;
   1.186 -        }
   1.187 -
   1.188 -        assert acc.mayRetry();
   1.189 -
   1.190 -        if (acc.hasBeenInvalidated()) {
   1.191 -            log.info("New chance for " + acc);
   1.192 -            acc.newSwitchPoint();
   1.193 -        }
   1.194 -
   1.195 -        assert !acc.hasBeenInvalidated();
   1.196 -
   1.197 -        // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter
   1.198 -        final MethodHandle target           = inv.getInvocation();
   1.199 -        final Class<?>     receiverType     = target.type().parameterType(0);
   1.200 -        final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP,  this);
   1.201 -        final MethodHandle invalidator      = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType));
   1.202 -        final MethodHandle mh               = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc));
   1.203 -
   1.204 -        assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints());
   1.205 -        log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint());
   1.206 -        return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException());
   1.207      }
   1.208  
   1.209      /**
   1.210 @@ -380,11 +419,11 @@
   1.211       *
   1.212       * @return resulting getter, or null if failed to create constant
   1.213       */
   1.214 -    synchronized GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) {
   1.215 +    GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) {
   1.216          // Only use constant getter for fast scope access, because the receiver may change between invocations
   1.217          // for slow-scope and non-scope callsites.
   1.218          // Also return null for user accessor properties as they may have side effects.
   1.219 -        if (!NashornCallSiteDescriptor.isFastScope(desc)
   1.220 +        if (invalidatedForever.get() || !NashornCallSiteDescriptor.isFastScope(desc)
   1.221                  || (GLOBAL_ONLY && !find.getOwner().isGlobal())
   1.222                  || find.getProperty() instanceof UserAccessorProperty) {
   1.223              return null;
   1.224 @@ -395,51 +434,53 @@
   1.225          final Class<?> retType      = desc.getMethodType().returnType();
   1.226          final String   name         = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
   1.227  
   1.228 -        final Access acc = getOrCreateSwitchPoint(name);
   1.229 +        synchronized (this) {
   1.230 +            final Access acc = getOrCreateSwitchPoint(name);
   1.231  
   1.232 -        log.fine("Starting to look up object value " + name);
   1.233 -        final Object c = find.getObjectValue();
   1.234 +            log.fine("Starting to look up object value " + name);
   1.235 +            final Object c = find.getObjectValue();
   1.236  
   1.237 -        if (log.isEnabled()) {
   1.238 -            log.fine("Trying to link constant GETTER " + acc + " value = " + c);
   1.239 +            if (log.isEnabled()) {
   1.240 +                log.fine("Trying to link constant GETTER " + acc + " value = " + c);
   1.241 +            }
   1.242 +
   1.243 +            if (acc.hasBeenInvalidated() || acc.guardFailed() || invalidatedForever.get()) {
   1.244 +                if (log.isEnabled()) {
   1.245 +                    log.info("*** GET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation());
   1.246 +                }
   1.247 +                return null;
   1.248 +            }
   1.249 +
   1.250 +            final MethodHandle cmh = constantGetter(c);
   1.251 +
   1.252 +            MethodHandle mh;
   1.253 +            MethodHandle guard;
   1.254 +
   1.255 +            if (isOptimistic) {
   1.256 +                if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) {
   1.257 +                    //widen return type - this is pessimistic, so it will always work
   1.258 +                    mh = MH.asType(cmh, cmh.type().changeReturnType(retType));
   1.259 +                } else {
   1.260 +                    //immediately invalidate - we asked for a too wide constant as a narrower one
   1.261 +                    mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class);
   1.262 +                }
   1.263 +            } else {
   1.264 +                //pessimistic return type filter
   1.265 +                mh = Lookup.filterReturnType(cmh, retType);
   1.266 +            }
   1.267 +
   1.268 +            if (find.getOwner().isGlobal()) {
   1.269 +                guard = null;
   1.270 +            } else {
   1.271 +                guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver);
   1.272 +            }
   1.273 +
   1.274 +            if (log.isEnabled()) {
   1.275 +                log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint());
   1.276 +                mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc);
   1.277 +            }
   1.278 +
   1.279 +            return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null);
   1.280          }
   1.281 -
   1.282 -        if (acc.hasBeenInvalidated() || acc.guardFailed()) {
   1.283 -            if (log.isEnabled()) {
   1.284 -                log.info("*** GET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation());
   1.285 -            }
   1.286 -            return null;
   1.287 -        }
   1.288 -
   1.289 -        final MethodHandle cmh = constantGetter(c);
   1.290 -
   1.291 -        MethodHandle mh;
   1.292 -        MethodHandle guard;
   1.293 -
   1.294 -        if (isOptimistic) {
   1.295 -            if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) {
   1.296 -                //widen return type - this is pessimistic, so it will always work
   1.297 -                mh = MH.asType(cmh, cmh.type().changeReturnType(retType));
   1.298 -            } else {
   1.299 -                //immediately invalidate - we asked for a too wide constant as a narrower one
   1.300 -                mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class);
   1.301 -            }
   1.302 -        } else {
   1.303 -            //pessimistic return type filter
   1.304 -            mh = Lookup.filterReturnType(cmh, retType);
   1.305 -        }
   1.306 -
   1.307 -        if (find.getOwner().isGlobal()) {
   1.308 -            guard = null;
   1.309 -        } else {
   1.310 -            guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver);
   1.311 -        }
   1.312 -
   1.313 -        if (log.isEnabled()) {
   1.314 -            log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint());
   1.315 -            mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc);
   1.316 -        }
   1.317 -
   1.318 -        return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null);
   1.319      }
   1.320  }

mercurial