Thu, 06 Nov 2014 17:06:56 +0100
8062308: Incorrect constant linkage with multiple Globals in a Context
Reviewed-by: lagergren, sundar
1.1 --- a/src/jdk/nashorn/internal/objects/Global.java Thu Nov 06 13:15:52 2014 +0100 1.2 +++ b/src/jdk/nashorn/internal/objects/Global.java Thu Nov 06 17:06:56 2014 +0100 1.3 @@ -29,6 +29,7 @@ 1.4 import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; 1.5 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 1.6 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 1.7 + 1.8 import java.io.IOException; 1.9 import java.io.PrintWriter; 1.10 import java.lang.invoke.MethodHandle; 1.11 @@ -41,7 +42,6 @@ 1.12 import java.util.Map; 1.13 import java.util.concurrent.Callable; 1.14 import java.util.concurrent.ConcurrentHashMap; 1.15 -import java.util.concurrent.atomic.AtomicReference; 1.16 import javax.script.ScriptContext; 1.17 import javax.script.ScriptEngine; 1.18 import jdk.internal.dynalink.linker.GuardedInvocation; 1.19 @@ -54,7 +54,6 @@ 1.20 import jdk.nashorn.internal.objects.annotations.ScriptClass; 1.21 import jdk.nashorn.internal.runtime.ConsString; 1.22 import jdk.nashorn.internal.runtime.Context; 1.23 -import jdk.nashorn.internal.runtime.GlobalConstants; 1.24 import jdk.nashorn.internal.runtime.GlobalFunctions; 1.25 import jdk.nashorn.internal.runtime.JSType; 1.26 import jdk.nashorn.internal.runtime.NativeJavaPackage; 1.27 @@ -438,9 +437,6 @@ 1.28 this.scontext = scontext; 1.29 } 1.30 1.31 - // global constants for this global - they can be replaced with MethodHandle.constant until invalidated 1.32 - private static AtomicReference<GlobalConstants> gcsInstance = new AtomicReference<>(); 1.33 - 1.34 @Override 1.35 protected Context getContext() { 1.36 return context; 1.37 @@ -470,11 +466,6 @@ 1.38 super(checkAndGetMap(context)); 1.39 this.context = context; 1.40 this.setIsScope(); 1.41 - //we can only share one instance of Global constants between globals, or we consume way too much 1.42 - //memory - this is good enough for most programs 1.43 - while (gcsInstance.get() == null) { 1.44 - gcsInstance.compareAndSet(null, new GlobalConstants(context.getLogger(GlobalConstants.class))); 1.45 - } 1.46 } 1.47 1.48 /** 1.49 @@ -493,15 +484,6 @@ 1.50 } 1.51 1.52 /** 1.53 - * Return the global constants map for fields that 1.54 - * can be accessed as MethodHandle.constant 1.55 - * @return constant map 1.56 - */ 1.57 - public static GlobalConstants getConstants() { 1.58 - return gcsInstance.get(); 1.59 - } 1.60 - 1.61 - /** 1.62 * Check if we have a Global instance 1.63 * @return true if one exists 1.64 */
2.1 --- a/src/jdk/nashorn/internal/runtime/Context.java Thu Nov 06 13:15:52 2014 +0100 2.2 +++ b/src/jdk/nashorn/internal/runtime/Context.java Thu Nov 06 17:06:56 2014 +0100 2.3 @@ -33,6 +33,7 @@ 2.4 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 2.5 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 2.6 import static jdk.nashorn.internal.runtime.Source.sourceFor; 2.7 + 2.8 import java.io.File; 2.9 import java.io.IOException; 2.10 import java.io.PrintWriter; 2.11 @@ -60,6 +61,7 @@ 2.12 import java.util.LinkedHashMap; 2.13 import java.util.Map; 2.14 import java.util.concurrent.atomic.AtomicLong; 2.15 +import java.util.concurrent.atomic.AtomicReference; 2.16 import java.util.function.Consumer; 2.17 import java.util.function.Supplier; 2.18 import java.util.logging.Level; 2.19 @@ -262,6 +264,10 @@ 2.20 // persistent code store 2.21 private CodeStore codeStore; 2.22 2.23 + // A factory for linking global properties as constant method handles. It is created when the first Global 2.24 + // is created, and invalidated forever once the second global is created. 2.25 + private final AtomicReference<GlobalConstants> globalConstantsRef = new AtomicReference<>(); 2.26 + 2.27 /** 2.28 * Get the current global scope 2.29 * @return the current global scope 2.30 @@ -293,7 +299,10 @@ 2.31 assert getGlobal() != global; 2.32 //same code can be cached between globals, then we need to invalidate method handle constants 2.33 if (global != null) { 2.34 - Global.getConstants().invalidateAll(); 2.35 + final GlobalConstants globalConstants = getContext(global).getGlobalConstants(); 2.36 + if (globalConstants != null) { 2.37 + globalConstants.invalidateAll(); 2.38 + } 2.39 } 2.40 currentGlobal.set(global); 2.41 } 2.42 @@ -529,6 +538,15 @@ 2.43 } 2.44 2.45 /** 2.46 + * Returns the factory for constant method handles for global properties. The returned factory can be 2.47 + * invalidated if this Context has more than one Global. 2.48 + * @return the factory for constant method handles for global properties. 2.49 + */ 2.50 + GlobalConstants getGlobalConstants() { 2.51 + return globalConstantsRef.get(); 2.52 + } 2.53 + 2.54 + /** 2.55 * Get the error manager for this context 2.56 * @return error manger 2.57 */ 2.58 @@ -1016,9 +1034,32 @@ 2.59 * @return the global script object 2.60 */ 2.61 public Global newGlobal() { 2.62 + createOrInvalidateGlobalConstants(); 2.63 return new Global(this); 2.64 } 2.65 2.66 + private void createOrInvalidateGlobalConstants() { 2.67 + for (;;) { 2.68 + final GlobalConstants currentGlobalConstants = getGlobalConstants(); 2.69 + if (currentGlobalConstants != null) { 2.70 + // Subsequent invocation; we're creating our second or later Global. GlobalConstants is not safe to use 2.71 + // with more than one Global, as the constant method handle linkages it creates create a coupling 2.72 + // between the Global and the call sites in the compiled code. 2.73 + currentGlobalConstants.invalidateForever(); 2.74 + return; 2.75 + } 2.76 + final GlobalConstants newGlobalConstants = new GlobalConstants(getLogger(GlobalConstants.class)); 2.77 + if (globalConstantsRef.compareAndSet(null, newGlobalConstants)) { 2.78 + // First invocation; we're creating the first Global in this Context. Create the GlobalConstants object 2.79 + // for this Context. 2.80 + return; 2.81 + } 2.82 + 2.83 + // If we reach here, then we started out as the first invocation, but another concurrent invocation won the 2.84 + // CAS race. We'll just let the loop repeat and invalidate the CAS race winner. 2.85 + } 2.86 + } 2.87 + 2.88 /** 2.89 * Initialize given global scope object. 2.90 * 2.91 @@ -1057,12 +1098,19 @@ 2.92 * @return current global's context 2.93 */ 2.94 static Context getContextTrusted() { 2.95 - return ((ScriptObject)Context.getGlobal()).getContext(); 2.96 + return getContext(getGlobal()); 2.97 } 2.98 2.99 static Context getContextTrustedOrNull() { 2.100 final Global global = Context.getGlobal(); 2.101 - return global == null ? null : ((ScriptObject)global).getContext(); 2.102 + return global == null ? null : getContext(global); 2.103 + } 2.104 + 2.105 + private static Context getContext(final Global global) { 2.106 + // We can't invoke Global.getContext() directly, as it's a protected override, and Global isn't in our package. 2.107 + // In order to access the method, we must cast it to ScriptObject first (which is in our package) and then let 2.108 + // virtual invocation do its thing. 2.109 + return ((ScriptObject)global).getContext(); 2.110 } 2.111 2.112 /**
3.1 --- a/src/jdk/nashorn/internal/runtime/GlobalConstants.java Thu Nov 06 13:15:52 2014 +0100 3.2 +++ b/src/jdk/nashorn/internal/runtime/GlobalConstants.java Thu Nov 06 17:06:56 2014 +0100 3.3 @@ -31,12 +31,14 @@ 3.4 import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; 3.5 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.getProgramPoint; 3.6 import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote; 3.7 + 3.8 import java.lang.invoke.MethodHandle; 3.9 import java.lang.invoke.MethodHandles; 3.10 import java.lang.invoke.SwitchPoint; 3.11 import java.util.Arrays; 3.12 import java.util.HashMap; 3.13 import java.util.Map; 3.14 +import java.util.concurrent.atomic.AtomicBoolean; 3.15 import java.util.logging.Level; 3.16 import jdk.internal.dynalink.CallSiteDescriptor; 3.17 import jdk.internal.dynalink.DynamicLinker; 3.18 @@ -50,7 +52,7 @@ 3.19 import jdk.nashorn.internal.runtime.logging.Logger; 3.20 3.21 /** 3.22 - * Each global owns one of these. This is basically table of accessors 3.23 + * Each context owns one of these. This is basically table of accessors 3.24 * for global properties. A global constant is evaluated to a MethodHandle.constant 3.25 * for faster access and to avoid walking to proto chain looking for it. 3.26 * 3.27 @@ -67,12 +69,19 @@ 3.28 * reregister the switchpoint. Set twice or more - don't try again forever, or we'd 3.29 * just end up relinking our way into megamorphisism. 3.30 * 3.31 + * Also it has to be noted that this kind of linking creates a coupling between a Global 3.32 + * and the call sites in compiled code belonging to the Context. For this reason, the 3.33 + * linkage becomes incorrect as soon as the Context has more than one Global. The 3.34 + * {@link #invalidateForever()} is invoked by the Context to invalidate all linkages and 3.35 + * turn off the functionality of this object as soon as the Context's {@link Context#newGlobal()} is invoked 3.36 + * for second time. 3.37 + * 3.38 * We can extend this to ScriptObjects in general (GLOBAL_ONLY=false), which requires 3.39 * a receiver guard on the constant getter, but it currently leaks memory and its benefits 3.40 * have not yet been investigated property. 3.41 * 3.42 - * As long as all Globals share the same constant instance, we need synchronization 3.43 - * whenever we access the instance. 3.44 + * As long as all Globals in a Context share the same GlobalConstants instance, we need synchronization 3.45 + * whenever we access it. 3.46 */ 3.47 @Logger(name="const") 3.48 public final class GlobalConstants implements Loggable { 3.49 @@ -82,7 +91,7 @@ 3.50 * Script objects require a receiver guard, which is memory intensive, so this is currently 3.51 * disabled. We might implement a weak reference based approach to this later. 3.52 */ 3.53 - private static final boolean GLOBAL_ONLY = true; 3.54 + public static final boolean GLOBAL_ONLY = true; 3.55 3.56 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 3.57 3.58 @@ -98,6 +107,8 @@ 3.59 */ 3.60 private final Map<String, Access> map = new HashMap<>(); 3.61 3.62 + private final AtomicBoolean invalidatedForever = new AtomicBoolean(false); 3.63 + 3.64 /** 3.65 * Constructor - used only by global 3.66 * @param log logger, or null if none 3.67 @@ -216,10 +227,34 @@ 3.68 * the same class for a new global, but the builtins and global scoped variables 3.69 * will have changed. 3.70 */ 3.71 - public synchronized void invalidateAll() { 3.72 - log.info("New global created - invalidating all constant callsites without increasing invocation count."); 3.73 - for (final Access acc : map.values()) { 3.74 - acc.invalidateUncounted(); 3.75 + public void invalidateAll() { 3.76 + if (!invalidatedForever.get()) { 3.77 + log.info("New global created - invalidating all constant callsites without increasing invocation count."); 3.78 + synchronized (this) { 3.79 + for (final Access acc : map.values()) { 3.80 + acc.invalidateUncounted(); 3.81 + } 3.82 + } 3.83 + } 3.84 + } 3.85 + 3.86 + /** 3.87 + * To avoid an expensive global guard "is this the same global", similar to the 3.88 + * receiver guard on the ScriptObject level, we invalidate all getters when the 3.89 + * second Global is created by the Context owning this instance. After this 3.90 + * method is invoked, this GlobalConstants instance will both invalidate all the 3.91 + * switch points it produced, and it will stop handing out new method handles 3.92 + * altogether. 3.93 + */ 3.94 + public void invalidateForever() { 3.95 + if (invalidatedForever.compareAndSet(false, true)) { 3.96 + log.info("New global created - invalidating all constant callsites."); 3.97 + synchronized (this) { 3.98 + for (final Access acc : map.values()) { 3.99 + acc.invalidateForever(); 3.100 + } 3.101 + map.clear(); 3.102 + } 3.103 } 3.104 } 3.105 3.106 @@ -251,7 +286,7 @@ 3.107 return obj; 3.108 } 3.109 3.110 - private synchronized Access getOrCreateSwitchPoint(final String name) { 3.111 + private Access getOrCreateSwitchPoint(final String name) { 3.112 Access acc = map.get(name); 3.113 if (acc != null) { 3.114 return acc; 3.115 @@ -267,9 +302,13 @@ 3.116 * @param name name of property 3.117 */ 3.118 void delete(final String name) { 3.119 - final Access acc = map.get(name); 3.120 - if (acc != null) { 3.121 - acc.invalidateForever(); 3.122 + if (!invalidatedForever.get()) { 3.123 + synchronized (this) { 3.124 + final Access acc = map.get(name); 3.125 + if (acc != null) { 3.126 + acc.invalidateForever(); 3.127 + } 3.128 + } 3.129 } 3.130 } 3.131 3.132 @@ -313,45 +352,45 @@ 3.133 * 3.134 * @return null if failed to set up constant linkage 3.135 */ 3.136 - synchronized GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) { 3.137 - if (GLOBAL_ONLY && !isGlobalSetter(receiver, find)) { 3.138 + GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) { 3.139 + if (invalidatedForever.get() || (GLOBAL_ONLY && !isGlobalSetter(receiver, find))) { 3.140 return null; 3.141 } 3.142 3.143 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 3.144 3.145 - final Access acc = getOrCreateSwitchPoint(name); 3.146 + synchronized (this) { 3.147 + final Access acc = getOrCreateSwitchPoint(name); 3.148 3.149 - if (log.isEnabled()) { 3.150 - log.fine("Trying to link constant SETTER ", acc); 3.151 + if (log.isEnabled()) { 3.152 + log.fine("Trying to link constant SETTER ", acc); 3.153 + } 3.154 + 3.155 + if (!acc.mayRetry() || invalidatedForever.get()) { 3.156 + if (log.isEnabled()) { 3.157 + log.fine("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); 3.158 + } 3.159 + return null; 3.160 + } 3.161 + 3.162 + if (acc.hasBeenInvalidated()) { 3.163 + log.info("New chance for " + acc); 3.164 + acc.newSwitchPoint(); 3.165 + } 3.166 + 3.167 + assert !acc.hasBeenInvalidated(); 3.168 + 3.169 + // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter 3.170 + final MethodHandle target = inv.getInvocation(); 3.171 + final Class<?> receiverType = target.type().parameterType(0); 3.172 + final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP, this); 3.173 + final MethodHandle invalidator = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType)); 3.174 + final MethodHandle mh = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc)); 3.175 + 3.176 + assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints()); 3.177 + log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint()); 3.178 + return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException()); 3.179 } 3.180 - 3.181 - if (!acc.mayRetry()) { 3.182 - if (log.isEnabled()) { 3.183 - log.fine("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); 3.184 - } 3.185 - return null; 3.186 - } 3.187 - 3.188 - assert acc.mayRetry(); 3.189 - 3.190 - if (acc.hasBeenInvalidated()) { 3.191 - log.info("New chance for " + acc); 3.192 - acc.newSwitchPoint(); 3.193 - } 3.194 - 3.195 - assert !acc.hasBeenInvalidated(); 3.196 - 3.197 - // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter 3.198 - final MethodHandle target = inv.getInvocation(); 3.199 - final Class<?> receiverType = target.type().parameterType(0); 3.200 - final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP, this); 3.201 - final MethodHandle invalidator = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType)); 3.202 - final MethodHandle mh = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc)); 3.203 - 3.204 - assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints()); 3.205 - log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint()); 3.206 - return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException()); 3.207 } 3.208 3.209 /** 3.210 @@ -380,11 +419,11 @@ 3.211 * 3.212 * @return resulting getter, or null if failed to create constant 3.213 */ 3.214 - synchronized GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) { 3.215 + GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) { 3.216 // Only use constant getter for fast scope access, because the receiver may change between invocations 3.217 // for slow-scope and non-scope callsites. 3.218 // Also return null for user accessor properties as they may have side effects. 3.219 - if (!NashornCallSiteDescriptor.isFastScope(desc) 3.220 + if (invalidatedForever.get() || !NashornCallSiteDescriptor.isFastScope(desc) 3.221 || (GLOBAL_ONLY && !find.getOwner().isGlobal()) 3.222 || find.getProperty() instanceof UserAccessorProperty) { 3.223 return null; 3.224 @@ -395,51 +434,53 @@ 3.225 final Class<?> retType = desc.getMethodType().returnType(); 3.226 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 3.227 3.228 - final Access acc = getOrCreateSwitchPoint(name); 3.229 + synchronized (this) { 3.230 + final Access acc = getOrCreateSwitchPoint(name); 3.231 3.232 - log.fine("Starting to look up object value " + name); 3.233 - final Object c = find.getObjectValue(); 3.234 + log.fine("Starting to look up object value " + name); 3.235 + final Object c = find.getObjectValue(); 3.236 3.237 - if (log.isEnabled()) { 3.238 - log.fine("Trying to link constant GETTER " + acc + " value = " + c); 3.239 + if (log.isEnabled()) { 3.240 + log.fine("Trying to link constant GETTER " + acc + " value = " + c); 3.241 + } 3.242 + 3.243 + if (acc.hasBeenInvalidated() || acc.guardFailed() || invalidatedForever.get()) { 3.244 + if (log.isEnabled()) { 3.245 + log.info("*** GET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); 3.246 + } 3.247 + return null; 3.248 + } 3.249 + 3.250 + final MethodHandle cmh = constantGetter(c); 3.251 + 3.252 + MethodHandle mh; 3.253 + MethodHandle guard; 3.254 + 3.255 + if (isOptimistic) { 3.256 + if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) { 3.257 + //widen return type - this is pessimistic, so it will always work 3.258 + mh = MH.asType(cmh, cmh.type().changeReturnType(retType)); 3.259 + } else { 3.260 + //immediately invalidate - we asked for a too wide constant as a narrower one 3.261 + mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class); 3.262 + } 3.263 + } else { 3.264 + //pessimistic return type filter 3.265 + mh = Lookup.filterReturnType(cmh, retType); 3.266 + } 3.267 + 3.268 + if (find.getOwner().isGlobal()) { 3.269 + guard = null; 3.270 + } else { 3.271 + guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver); 3.272 + } 3.273 + 3.274 + if (log.isEnabled()) { 3.275 + log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint()); 3.276 + mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc); 3.277 + } 3.278 + 3.279 + return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null); 3.280 } 3.281 - 3.282 - if (acc.hasBeenInvalidated() || acc.guardFailed()) { 3.283 - if (log.isEnabled()) { 3.284 - log.info("*** GET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); 3.285 - } 3.286 - return null; 3.287 - } 3.288 - 3.289 - final MethodHandle cmh = constantGetter(c); 3.290 - 3.291 - MethodHandle mh; 3.292 - MethodHandle guard; 3.293 - 3.294 - if (isOptimistic) { 3.295 - if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) { 3.296 - //widen return type - this is pessimistic, so it will always work 3.297 - mh = MH.asType(cmh, cmh.type().changeReturnType(retType)); 3.298 - } else { 3.299 - //immediately invalidate - we asked for a too wide constant as a narrower one 3.300 - mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class); 3.301 - } 3.302 - } else { 3.303 - //pessimistic return type filter 3.304 - mh = Lookup.filterReturnType(cmh, retType); 3.305 - } 3.306 - 3.307 - if (find.getOwner().isGlobal()) { 3.308 - guard = null; 3.309 - } else { 3.310 - guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver); 3.311 - } 3.312 - 3.313 - if (log.isEnabled()) { 3.314 - log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint()); 3.315 - mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc); 3.316 - } 3.317 - 3.318 - return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null); 3.319 } 3.320 }
4.1 --- a/src/jdk/nashorn/internal/runtime/ScriptObject.java Thu Nov 06 13:15:52 2014 +0100 4.2 +++ b/src/jdk/nashorn/internal/runtime/ScriptObject.java Thu Nov 06 17:06:56 2014 +0100 4.3 @@ -47,6 +47,7 @@ 4.4 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; 4.5 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; 4.6 import static jdk.nashorn.internal.runtime.linker.NashornGuards.explicitInstanceOfCheck; 4.7 + 4.8 import java.lang.invoke.MethodHandle; 4.9 import java.lang.invoke.MethodHandles; 4.10 import java.lang.invoke.MethodType; 4.11 @@ -922,7 +923,10 @@ 4.12 if (property instanceof UserAccessorProperty) { 4.13 ((UserAccessorProperty)property).setAccessors(this, getMap(), null); 4.14 } 4.15 - Global.getConstants().delete(property.getKey()); 4.16 + final GlobalConstants globalConstants = getGlobalConstants(); 4.17 + if (globalConstants != null) { 4.18 + globalConstants.delete(property.getKey()); 4.19 + } 4.20 return true; 4.21 } 4.22 } 4.23 @@ -1983,9 +1987,12 @@ 4.24 } 4.25 } 4.26 4.27 - final GuardedInvocation cinv = Global.getConstants().findGetMethod(find, this, desc); 4.28 - if (cinv != null) { 4.29 - return cinv; 4.30 + final GlobalConstants globalConstants = getGlobalConstants(); 4.31 + if (globalConstants != null) { 4.32 + final GuardedInvocation cinv = globalConstants.findGetMethod(find, this, desc); 4.33 + if (cinv != null) { 4.34 + return cinv; 4.35 + } 4.36 } 4.37 4.38 final Class<?> returnType = desc.getMethodType().returnType(); 4.39 @@ -2183,14 +2190,22 @@ 4.40 4.41 final GuardedInvocation inv = new SetMethodCreator(this, find, desc, request).createGuardedInvocation(findBuiltinSwitchPoint(name)); 4.42 4.43 - final GuardedInvocation cinv = Global.getConstants().findSetMethod(find, this, inv, desc, request); 4.44 - if (cinv != null) { 4.45 - return cinv; 4.46 + final GlobalConstants globalConstants = getGlobalConstants(); 4.47 + if (globalConstants != null) { 4.48 + final GuardedInvocation cinv = globalConstants.findSetMethod(find, this, inv, desc, request); 4.49 + if (cinv != null) { 4.50 + return cinv; 4.51 + } 4.52 } 4.53 4.54 return inv; 4.55 } 4.56 4.57 + private GlobalConstants getGlobalConstants() { 4.58 + // Avoid hitting getContext() which might be costly for a non-Global unless needed. 4.59 + return GlobalConstants.GLOBAL_ONLY && !isGlobal() ? null : getContext().getGlobalConstants(); 4.60 + } 4.61 + 4.62 private GuardedInvocation createEmptySetMethod(final CallSiteDescriptor desc, final boolean explicitInstanceOfCheck, final String strictErrorMessage, final boolean canBeFastScope) { 4.63 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 4.64 if (NashornCallSiteDescriptor.isStrict(desc)) {