GlobalConstants.java revision 1036:f0b5e3900a10
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.logging.Level; 42import jdk.internal.dynalink.CallSiteDescriptor; 43import jdk.internal.dynalink.DynamicLinker; 44import jdk.internal.dynalink.linker.GuardedInvocation; 45import jdk.internal.dynalink.linker.LinkRequest; 46import jdk.nashorn.internal.lookup.Lookup; 47import jdk.nashorn.internal.lookup.MethodHandleFactory; 48import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 49import jdk.nashorn.internal.runtime.logging.DebugLogger; 50import jdk.nashorn.internal.runtime.logging.Loggable; 51import jdk.nashorn.internal.runtime.logging.Logger; 52 53/** 54 * Each global owns one of these. This is basically table of accessors 55 * for global properties. A global constant is evaluated to a MethodHandle.constant 56 * for faster access and to avoid walking to proto chain looking for it. 57 * 58 * We put a switchpoint on the global setter, which invalidates the 59 * method handle constant getters, and reverts to the standard access strategy 60 * 61 * However, there is a twist - while certain globals like "undefined" and "Math" 62 * are usually never reassigned, a global value can be reset once, and never again. 63 * This is a rather common pattern, like: 64 * 65 * x = function(something) { ... 66 * 67 * Thus everything registered as a global constant gets an extra chance. Set once, 68 * reregister the switchpoint. Set twice or more - don't try again forever, or we'd 69 * just end up relinking our way into megamorphisism. 70 * 71 * We can extend this to ScriptObjects in general (GLOBAL_ONLY=false), which requires 72 * a receiver guard on the constant getter, but it currently leaks memory and its benefits 73 * have not yet been investigated property. 74 * 75 * As long as all Globals share the same constant instance, we need synchronization 76 * whenever we access the instance. 77 */ 78@Logger(name="const") 79public final class GlobalConstants implements Loggable { 80 81 /** 82 * Should we only try to link globals as constants, and not generic script objects. 83 * Script objects require a receiver guard, which is memory intensive, so this is currently 84 * disabled. We might implement a weak reference based approach to this later. 85 */ 86 private static final boolean GLOBAL_ONLY = true; 87 88 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 89 90 private static final MethodHandle INVALIDATE_SP = virtualCall(LOOKUP, GlobalConstants.class, "invalidateSwitchPoint", Object.class, Object.class, Access.class).methodHandle(); 91 private static final MethodHandle RECEIVER_GUARD = staticCall(LOOKUP, GlobalConstants.class, "receiverGuard", boolean.class, Access.class, Object.class, Object.class).methodHandle(); 92 93 /** Logger for constant getters */ 94 private final DebugLogger log; 95 96 /** 97 * Access map for this global - associates a symbol name with an Access object, with getter 98 * and invalidation information 99 */ 100 private final Map<String, Access> map = new HashMap<>(); 101 102 /** 103 * Constructor - used only by global 104 * @param log logger, or null if none 105 */ 106 public GlobalConstants(final DebugLogger log) { 107 this.log = log == null ? DebugLogger.DISABLED_LOGGER : log; 108 } 109 110 @Override 111 public DebugLogger getLogger() { 112 return log; 113 } 114 115 @Override 116 public DebugLogger initLogger(final Context context) { 117 return DebugLogger.DISABLED_LOGGER; 118 } 119 120 /** 121 * Information about a constant access and its potential invalidations 122 */ 123 private static class Access { 124 /** name of symbol */ 125 private final String name; 126 127 /** switchpoint that invalidates the getters and setters for this access */ 128 private SwitchPoint sp; 129 130 /** invalidation count for this access, i.e. how many times has this property been reset */ 131 private int invalidations; 132 133 /** has a guard guarding this property getter failed? */ 134 private boolean guardFailed; 135 136 private static final int MAX_RETRIES = 2; 137 138 private Access(final String name, final SwitchPoint sp) { 139 this.name = name; 140 this.sp = sp; 141 } 142 143 private boolean hasBeenInvalidated() { 144 return sp.hasBeenInvalidated(); 145 } 146 147 private boolean guardFailed() { 148 return guardFailed; 149 } 150 151 private void failGuard() { 152 invalidateOnce(); 153 guardFailed = true; 154 } 155 156 private void newSwitchPoint() { 157 assert hasBeenInvalidated(); 158 sp = new SwitchPoint(); 159 } 160 161 private void invalidate(final int count) { 162 if (!sp.hasBeenInvalidated()) { 163 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 164 invalidations += count; 165 } 166 } 167 168 /** 169 * Invalidate the access, but do not contribute to the invalidation count 170 */ 171 private void invalidateUncounted() { 172 invalidate(0); 173 } 174 175 /** 176 * Invalidate the access, and contribute 1 to the invalidation count 177 */ 178 private void invalidateOnce() { 179 invalidate(1); 180 } 181 182 /** 183 * Invalidate the access and make sure that we never try to turn this into 184 * a MethodHandle.constant getter again 185 */ 186 private void invalidateForever() { 187 invalidate(MAX_RETRIES); 188 } 189 190 /** 191 * Are we allowed to relink this as constant getter, even though it 192 * it has been reset 193 * @return true if we can relink as constant, one retry is allowed 194 */ 195 private boolean mayRetry() { 196 return invalidations < MAX_RETRIES; 197 } 198 199 @Override 200 public String toString() { 201 return "[" + quote(name) + " <id=" + Debug.id(this) + "> inv#=" + invalidations + '/' + MAX_RETRIES + " sp_inv=" + sp.hasBeenInvalidated() + ']'; 202 } 203 204 String getName() { 205 return name; 206 } 207 208 SwitchPoint getSwitchPoint() { 209 return sp; 210 } 211 } 212 213 /** 214 * To avoid an expensive global guard "is this the same global", similar to the 215 * receiver guard on the ScriptObject level, we invalidate all getters once 216 * when we switch globals. This is used from the class cache. We _can_ reuse 217 * the same class for a new global, but the builtins and global scoped variables 218 * will have changed. 219 */ 220 public synchronized void invalidateAll() { 221 log.info("New global created - invalidating all constant callsites without increasing invocation count."); 222 for (final Access acc : map.values()) { 223 acc.invalidateUncounted(); 224 } 225 } 226 227 /** 228 * Invalidate the switchpoint of an access - we have written to 229 * the property 230 * 231 * @param obj receiver 232 * @param acc access 233 * 234 * @return receiver, so this can be used as param filter 235 */ 236 @SuppressWarnings("unused") 237 private synchronized Object invalidateSwitchPoint(final Object obj, final Access acc) { 238 if (log.isEnabled()) { 239 log.info("*** Invalidating switchpoint " + acc.getSwitchPoint() + " for receiver=" + obj + " access=" + acc); 240 } 241 acc.invalidateOnce(); 242 if (acc.mayRetry()) { 243 if (log.isEnabled()) { 244 log.info("Retry is allowed for " + acc + "... Creating a new switchpoint."); 245 } 246 acc.newSwitchPoint(); 247 } else { 248 if (log.isEnabled()) { 249 log.info("This was the last time I allowed " + quote(acc.getName()) + " to relink as constant."); 250 } 251 } 252 return obj; 253 } 254 255 private synchronized Access getOrCreateSwitchPoint(final String name) { 256 Access acc = map.get(name); 257 if (acc != null) { 258 return acc; 259 } 260 final SwitchPoint sp = new SwitchPoint(); 261 map.put(name, acc = new Access(name, sp)); 262 return acc; 263 } 264 265 /** 266 * Called from script object on property deletion to erase a property 267 * that might be linked as MethodHandle.constant and force relink 268 * @param name name of property 269 */ 270 void delete(final String name) { 271 final Access acc = map.get(name); 272 if (acc != null) { 273 acc.invalidateForever(); 274 } 275 } 276 277 /** 278 * Receiver guard is used if we extend the global constants to script objects in general. 279 * As the property can have different values in different script objects, while Global is 280 * by definition a singleton, we need this for ScriptObject constants (currently disabled) 281 * 282 * TODO: Note - this seems to cause memory leaks. Use weak references? But what is leaking seems 283 * to be the Access objects, which isn't the case for Globals. Weird. 284 * 285 * @param acc access 286 * @param boundReceiver the receiver bound to the callsite 287 * @param receiver the receiver to check against 288 * 289 * @return true if this receiver is still the one we bound to the callsite 290 */ 291 @SuppressWarnings("unused") 292 private static boolean receiverGuard(final Access acc, final Object boundReceiver, final Object receiver) { 293 final boolean id = receiver == boundReceiver; 294 if (!id) { 295 acc.failGuard(); 296 } 297 return id; 298 } 299 300 private static boolean isGlobalSetter(final ScriptObject receiver, final FindProperty find) { 301 if (find == null) { 302 return receiver.isScope(); 303 } 304 return find.getOwner().isGlobal(); 305 } 306 307 /** 308 * Augment a setter with switchpoint for invalidating its getters, should the setter be called 309 * 310 * @param find property lookup 311 * @param inv normal guarded invocation for this setter, as computed by the ScriptObject linker 312 * @param desc callsite descriptor 313 * @param request link request 314 * 315 * @return null if failed to set up constant linkage 316 */ 317 synchronized GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) { 318 if (GLOBAL_ONLY && !isGlobalSetter(receiver, find)) { 319 return null; 320 } 321 322 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 323 324 final Access acc = getOrCreateSwitchPoint(name); 325 326 if (log.isEnabled()) { 327 log.fine("Trying to link constant SETTER ", acc); 328 } 329 330 if (!acc.mayRetry()) { 331 log.info("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); 332 return null; 333 } 334 335 assert acc.mayRetry(); 336 337 if (acc.hasBeenInvalidated()) { 338 log.info("New chance for " + acc); 339 acc.newSwitchPoint(); 340 } 341 342 assert !acc.hasBeenInvalidated(); 343 344 // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter 345 final MethodHandle target = inv.getInvocation(); 346 final Class<?> receiverType = target.type().parameterType(0); 347 final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP, this); 348 final MethodHandle invalidator = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType)); 349 final MethodHandle mh = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc)); 350 351 assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints()); 352 log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint()); 353 return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException()); 354 } 355 356 /** 357 * Try to reuse constant method handles for getters 358 * @param c constant value 359 * @return method handle (with dummy receiver) that returns this constant 360 */ 361 public static MethodHandle staticConstantGetter(final Object c) { 362 return MH.dropArguments(JSType.unboxConstant(c), 0, Object.class); 363 } 364 365 private MethodHandle constantGetter(final Object c) { 366 final MethodHandle mh = staticConstantGetter(c); 367 if (log.isEnabled()) { 368 return MethodHandleFactory.addDebugPrintout(log, Level.FINEST, mh, "getting as constant"); 369 } 370 return mh; 371 } 372 373 /** 374 * Try to turn a getter into a MethodHandle.constant, if possible 375 * 376 * @param find property lookup 377 * @param receiver receiver 378 * @param desc callsite descriptor 379 * 380 * @return resulting getter, or null if failed to create constant 381 */ 382 synchronized GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) { 383 // Only use constant getter for fast scope access, because the receiver may change between invocations 384 // for slow-scope and non-scope callsites. 385 // Also return null for user accessor properties as they may have side effects. 386 if (!NashornCallSiteDescriptor.isFastScope(desc) 387 || (GLOBAL_ONLY && !find.getOwner().isGlobal()) 388 || find.getProperty() instanceof UserAccessorProperty) { 389 return null; 390 } 391 392 final boolean isOptimistic = NashornCallSiteDescriptor.isOptimistic(desc); 393 final int programPoint = isOptimistic ? getProgramPoint(desc) : INVALID_PROGRAM_POINT; 394 final Class<?> retType = desc.getMethodType().returnType(); 395 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 396 397 final Access acc = getOrCreateSwitchPoint(name); 398 399 log.fine("Starting to look up object value " + name); 400 final Object c = find.getObjectValue(); 401 402 if (log.isEnabled()) { 403 log.fine("Trying to link constant GETTER " + acc + " value = " + c); 404 } 405 406 if (acc.hasBeenInvalidated() || acc.guardFailed()) { 407 log.fine("*** GET: Giving up on " + quote(name) + " - retry count has exceeded"); 408 return null; 409 } 410 411 final MethodHandle cmh = constantGetter(c); 412 413 MethodHandle mh; 414 MethodHandle guard; 415 416 if (isOptimistic) { 417 if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) { 418 //widen return type - this is pessimistic, so it will always work 419 mh = MH.asType(cmh, cmh.type().changeReturnType(retType)); 420 } else { 421 //immediately invalidate - we asked for a too wide constant as a narrower one 422 mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class); 423 } 424 } else { 425 //pessimistic return type filter 426 mh = Lookup.filterReturnType(cmh, retType); 427 } 428 429 if (find.getOwner().isGlobal()) { 430 guard = null; 431 } else { 432 guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver); 433 } 434 435 if (log.isEnabled()) { 436 log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint()); 437 mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc); 438 } 439 440 return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null); 441 } 442} 443