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 }