GlobalConstants.java revision 1423:c13179703f65
1/* 2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.nashorn.internal.runtime; 27 28import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; 29import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall; 30import static jdk.nashorn.internal.lookup.Lookup.MH; 31import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; 32import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.getProgramPoint; 33import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote; 34 35import java.lang.invoke.MethodHandle; 36import java.lang.invoke.MethodHandles; 37import java.lang.invoke.SwitchPoint; 38import java.util.Arrays; 39import java.util.HashMap; 40import java.util.Map; 41import java.util.concurrent.atomic.AtomicBoolean; 42import java.util.logging.Level; 43import jdk.internal.dynalink.CallSiteDescriptor; 44import jdk.internal.dynalink.DynamicLinker; 45import jdk.internal.dynalink.linker.GuardedInvocation; 46import jdk.internal.dynalink.linker.LinkRequest; 47import jdk.nashorn.internal.lookup.Lookup; 48import jdk.nashorn.internal.lookup.MethodHandleFactory; 49import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 50import jdk.nashorn.internal.runtime.logging.DebugLogger; 51import jdk.nashorn.internal.runtime.logging.Loggable; 52import jdk.nashorn.internal.runtime.logging.Logger; 53 54/** 55 * Each context owns one of these. This is basically table of accessors 56 * for global properties. A global constant is evaluated to a MethodHandle.constant 57 * for faster access and to avoid walking to proto chain looking for it. 58 * 59 * We put a switchpoint on the global setter, which invalidates the 60 * method handle constant getters, and reverts to the standard access strategy 61 * 62 * However, there is a twist - while certain globals like "undefined" and "Math" 63 * are usually never reassigned, a global value can be reset once, and never again. 64 * This is a rather common pattern, like: 65 * 66 * x = function(something) { ... 67 * 68 * Thus everything registered as a global constant gets an extra chance. Set once, 69 * reregister the switchpoint. Set twice or more - don't try again forever, or we'd 70 * just end up relinking our way into megamorphism. 71 * 72 * Also it has to be noted that this kind of linking creates a coupling between a Global 73 * and the call sites in compiled code belonging to the Context. For this reason, the 74 * linkage becomes incorrect as soon as the Context has more than one Global. The 75 * {@link #invalidateForever()} is invoked by the Context to invalidate all linkages and 76 * turn off the functionality of this object as soon as the Context's {@link Context#newGlobal()} is invoked 77 * for second time. 78 * 79 * We can extend this to ScriptObjects in general (GLOBAL_ONLY=false), which requires 80 * a receiver guard on the constant getter, but it currently leaks memory and its benefits 81 * have not yet been investigated property. 82 * 83 * As long as all Globals in a Context share the same GlobalConstants instance, we need synchronization 84 * whenever we access it. 85 */ 86@Logger(name="const") 87public final class GlobalConstants implements Loggable { 88 89 /** 90 * Should we only try to link globals as constants, and not generic script objects. 91 * Script objects require a receiver guard, which is memory intensive, so this is currently 92 * disabled. We might implement a weak reference based approach to this later. 93 */ 94 public static final boolean GLOBAL_ONLY = true; 95 96 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 97 98 private static final MethodHandle INVALIDATE_SP = virtualCall(LOOKUP, GlobalConstants.class, "invalidateSwitchPoint", Object.class, Object.class, Access.class).methodHandle(); 99 private static final MethodHandle RECEIVER_GUARD = staticCall(LOOKUP, GlobalConstants.class, "receiverGuard", boolean.class, Access.class, Object.class, Object.class).methodHandle(); 100 101 /** Logger for constant getters */ 102 private final DebugLogger log; 103 104 /** 105 * Access map for this global - associates a symbol name with an Access object, with getter 106 * and invalidation information 107 */ 108 private final Map<String, Access> map = new HashMap<>(); 109 110 private final AtomicBoolean invalidatedForever = new AtomicBoolean(false); 111 112 /** 113 * Constructor - used only by global 114 * @param log logger, or null if none 115 */ 116 public GlobalConstants(final DebugLogger log) { 117 this.log = log == null ? DebugLogger.DISABLED_LOGGER : log; 118 } 119 120 @Override 121 public DebugLogger getLogger() { 122 return log; 123 } 124 125 @Override 126 public DebugLogger initLogger(final Context context) { 127 return DebugLogger.DISABLED_LOGGER; 128 } 129 130 /** 131 * Information about a constant access and its potential invalidations 132 */ 133 private static class Access { 134 /** name of symbol */ 135 private final String name; 136 137 /** switchpoint that invalidates the getters and setters for this access */ 138 private SwitchPoint sp; 139 140 /** invalidation count for this access, i.e. how many times has this property been reset */ 141 private int invalidations; 142 143 /** has a guard guarding this property getter failed? */ 144 private boolean guardFailed; 145 146 private static final int MAX_RETRIES = 2; 147 148 private Access(final String name, final SwitchPoint sp) { 149 this.name = name; 150 this.sp = sp; 151 } 152 153 private boolean hasBeenInvalidated() { 154 return sp.hasBeenInvalidated(); 155 } 156 157 private boolean guardFailed() { 158 return guardFailed; 159 } 160 161 private void failGuard() { 162 invalidateOnce(); 163 guardFailed = true; 164 } 165 166 private void newSwitchPoint() { 167 assert hasBeenInvalidated(); 168 sp = new SwitchPoint(); 169 } 170 171 private void invalidate(final int count) { 172 if (!sp.hasBeenInvalidated()) { 173 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 174 invalidations += count; 175 } 176 } 177 178 /** 179 * Invalidate the access, but do not contribute to the invalidation count 180 */ 181 private void invalidateUncounted() { 182 invalidate(0); 183 } 184 185 /** 186 * Invalidate the access, and contribute 1 to the invalidation count 187 */ 188 private void invalidateOnce() { 189 invalidate(1); 190 } 191 192 /** 193 * Invalidate the access and make sure that we never try to turn this into 194 * a MethodHandle.constant getter again 195 */ 196 private void invalidateForever() { 197 invalidate(MAX_RETRIES); 198 } 199 200 /** 201 * Are we allowed to relink this as constant getter, even though it 202 * it has been reset 203 * @return true if we can relink as constant, one retry is allowed 204 */ 205 private boolean mayRetry() { 206 return invalidations < MAX_RETRIES; 207 } 208 209 @Override 210 public String toString() { 211 return "[" + quote(name) + " <id=" + Debug.id(this) + "> inv#=" + invalidations + '/' + MAX_RETRIES + " sp_inv=" + sp.hasBeenInvalidated() + ']'; 212 } 213 214 String getName() { 215 return name; 216 } 217 218 SwitchPoint getSwitchPoint() { 219 return sp; 220 } 221 } 222 223 /** 224 * To avoid an expensive global guard "is this the same global", similar to the 225 * receiver guard on the ScriptObject level, we invalidate all getters once 226 * when we switch globals. This is used from the class cache. We _can_ reuse 227 * the same class for a new global, but the builtins and global scoped variables 228 * will have changed. 229 */ 230 public void invalidateAll() { 231 if (!invalidatedForever.get()) { 232 log.info("New global created - invalidating all constant callsites without increasing invocation count."); 233 synchronized (this) { 234 for (final Access acc : map.values()) { 235 acc.invalidateUncounted(); 236 } 237 } 238 } 239 } 240 241 /** 242 * To avoid an expensive global guard "is this the same global", similar to the 243 * receiver guard on the ScriptObject level, we invalidate all getters when the 244 * second Global is created by the Context owning this instance. After this 245 * method is invoked, this GlobalConstants instance will both invalidate all the 246 * switch points it produced, and it will stop handing out new method handles 247 * altogether. 248 */ 249 public void invalidateForever() { 250 if (invalidatedForever.compareAndSet(false, true)) { 251 log.info("New global created - invalidating all constant callsites."); 252 synchronized (this) { 253 for (final Access acc : map.values()) { 254 acc.invalidateForever(); 255 } 256 map.clear(); 257 } 258 } 259 } 260 261 /** 262 * Invalidate the switchpoint of an access - we have written to 263 * the property 264 * 265 * @param obj receiver 266 * @param acc access 267 * 268 * @return receiver, so this can be used as param filter 269 */ 270 @SuppressWarnings("unused") 271 private synchronized Object invalidateSwitchPoint(final Object obj, final Access acc) { 272 if (log.isEnabled()) { 273 log.info("*** Invalidating switchpoint " + acc.getSwitchPoint() + " for receiver=" + obj + " access=" + acc); 274 } 275 acc.invalidateOnce(); 276 if (acc.mayRetry()) { 277 if (log.isEnabled()) { 278 log.info("Retry is allowed for " + acc + "... Creating a new switchpoint."); 279 } 280 acc.newSwitchPoint(); 281 } else { 282 if (log.isEnabled()) { 283 log.info("This was the last time I allowed " + quote(acc.getName()) + " to relink as constant."); 284 } 285 } 286 return obj; 287 } 288 289 private Access getOrCreateSwitchPoint(final String name) { 290 Access acc = map.get(name); 291 if (acc != null) { 292 return acc; 293 } 294 final SwitchPoint sp = new SwitchPoint(); 295 map.put(name, acc = new Access(name, sp)); 296 return acc; 297 } 298 299 /** 300 * Called from script object on property deletion to erase a property 301 * that might be linked as MethodHandle.constant and force relink 302 * @param name name of property 303 */ 304 void delete(final String name) { 305 if (!invalidatedForever.get()) { 306 synchronized (this) { 307 final Access acc = map.get(name); 308 if (acc != null) { 309 acc.invalidateForever(); 310 } 311 } 312 } 313 } 314 315 /** 316 * Receiver guard is used if we extend the global constants to script objects in general. 317 * As the property can have different values in different script objects, while Global is 318 * by definition a singleton, we need this for ScriptObject constants (currently disabled) 319 * 320 * TODO: Note - this seems to cause memory leaks. Use weak references? But what is leaking seems 321 * to be the Access objects, which isn't the case for Globals. Weird. 322 * 323 * @param acc access 324 * @param boundReceiver the receiver bound to the callsite 325 * @param receiver the receiver to check against 326 * 327 * @return true if this receiver is still the one we bound to the callsite 328 */ 329 @SuppressWarnings("unused") 330 private static boolean receiverGuard(final Access acc, final Object boundReceiver, final Object receiver) { 331 final boolean id = receiver == boundReceiver; 332 if (!id) { 333 acc.failGuard(); 334 } 335 return id; 336 } 337 338 private static boolean isGlobalSetter(final ScriptObject receiver, final FindProperty find) { 339 if (find == null) { 340 return receiver.isScope(); 341 } 342 return find.getOwner().isGlobal(); 343 } 344 345 /** 346 * Augment a setter with switchpoint for invalidating its getters, should the setter be called 347 * 348 * @param find property lookup 349 * @param inv normal guarded invocation for this setter, as computed by the ScriptObject linker 350 * @param desc callsite descriptor 351 * @param request link request 352 * 353 * @return null if failed to set up constant linkage 354 */ 355 GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) { 356 if (invalidatedForever.get() || (GLOBAL_ONLY && !isGlobalSetter(receiver, find))) { 357 return null; 358 } 359 360 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 361 362 synchronized (this) { 363 final Access acc = getOrCreateSwitchPoint(name); 364 365 if (log.isEnabled()) { 366 log.fine("Trying to link constant SETTER ", acc); 367 } 368 369 if (!acc.mayRetry() || invalidatedForever.get()) { 370 if (log.isEnabled()) { 371 log.fine("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); 372 } 373 return null; 374 } 375 376 if (acc.hasBeenInvalidated()) { 377 log.info("New chance for " + acc); 378 acc.newSwitchPoint(); 379 } 380 381 assert !acc.hasBeenInvalidated(); 382 383 // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter 384 final MethodHandle target = inv.getInvocation(); 385 final Class<?> receiverType = target.type().parameterType(0); 386 final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP, this); 387 final MethodHandle invalidator = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType)); 388 final MethodHandle mh = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc)); 389 390 assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints()); 391 log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint()); 392 return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException()); 393 } 394 } 395 396 /** 397 * Try to reuse constant method handles for getters 398 * @param c constant value 399 * @return method handle (with dummy receiver) that returns this constant 400 */ 401 public static MethodHandle staticConstantGetter(final Object c) { 402 return MH.dropArguments(JSType.unboxConstant(c), 0, Object.class); 403 } 404 405 private MethodHandle constantGetter(final Object c) { 406 final MethodHandle mh = staticConstantGetter(c); 407 if (log.isEnabled()) { 408 return MethodHandleFactory.addDebugPrintout(log, Level.FINEST, mh, "getting as constant"); 409 } 410 return mh; 411 } 412 413 /** 414 * Try to turn a getter into a MethodHandle.constant, if possible 415 * 416 * @param find property lookup 417 * @param receiver receiver 418 * @param desc callsite descriptor 419 * 420 * @return resulting getter, or null if failed to create constant 421 */ 422 GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) { 423 // Only use constant getter for fast scope access, because the receiver may change between invocations 424 // for slow-scope and non-scope callsites. 425 // Also return null for user accessor properties as they may have side effects. 426 if (invalidatedForever.get() || !NashornCallSiteDescriptor.isFastScope(desc) 427 || (GLOBAL_ONLY && !find.getOwner().isGlobal()) 428 || find.getProperty() instanceof UserAccessorProperty) { 429 return null; 430 } 431 432 final boolean isOptimistic = NashornCallSiteDescriptor.isOptimistic(desc); 433 final int programPoint = isOptimistic ? getProgramPoint(desc) : INVALID_PROGRAM_POINT; 434 final Class<?> retType = desc.getMethodType().returnType(); 435 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 436 437 synchronized (this) { 438 final Access acc = getOrCreateSwitchPoint(name); 439 440 log.fine("Starting to look up object value " + name); 441 final Object c = find.getObjectValue(); 442 443 if (log.isEnabled()) { 444 log.fine("Trying to link constant GETTER " + acc + " value = " + c); 445 } 446 447 if (acc.hasBeenInvalidated() || acc.guardFailed() || invalidatedForever.get()) { 448 if (log.isEnabled()) { 449 log.info("*** GET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); 450 } 451 return null; 452 } 453 454 final MethodHandle cmh = constantGetter(c); 455 456 MethodHandle mh; 457 MethodHandle guard; 458 459 if (isOptimistic) { 460 if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) { 461 //widen return type - this is pessimistic, so it will always work 462 mh = MH.asType(cmh, cmh.type().changeReturnType(retType)); 463 } else { 464 //immediately invalidate - we asked for a too wide constant as a narrower one 465 mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class); 466 } 467 } else { 468 //pessimistic return type filter 469 mh = Lookup.filterReturnType(cmh, retType); 470 } 471 472 if (find.getOwner().isGlobal()) { 473 guard = null; 474 } else { 475 guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver); 476 } 477 478 if (log.isEnabled()) { 479 log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint()); 480 mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc); 481 } 482 483 return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null); 484 } 485 } 486} 487