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