LinkerCallSite.java revision 953:221a84ef44c0
1/* 2 * Copyright (c) 2010, 2013, 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.linker; 27 28import static jdk.nashorn.internal.lookup.Lookup.MH; 29 30import java.io.FileNotFoundException; 31import java.io.FileOutputStream; 32import java.io.PrintWriter; 33import java.lang.invoke.MethodHandle; 34import java.lang.invoke.MethodHandles; 35import java.lang.invoke.MethodType; 36import java.util.ArrayList; 37import java.util.Collections; 38import java.util.Comparator; 39import java.util.HashMap; 40import java.util.LinkedList; 41import java.util.Map; 42import java.util.Map.Entry; 43import java.util.Random; 44import java.util.Set; 45import java.util.concurrent.atomic.AtomicInteger; 46import jdk.internal.dynalink.ChainedCallSite; 47import jdk.internal.dynalink.DynamicLinker; 48import jdk.internal.dynalink.linker.GuardedInvocation; 49import jdk.nashorn.internal.runtime.Context; 50import jdk.nashorn.internal.runtime.Debug; 51import jdk.nashorn.internal.runtime.ScriptObject; 52import jdk.nashorn.internal.runtime.ScriptRuntime; 53import jdk.nashorn.internal.runtime.options.Options; 54 55 56/** 57 * Relinkable form of call site. 58 */ 59public class LinkerCallSite extends ChainedCallSite { 60 /** Maximum number of arguments passed directly. */ 61 public static final int ARGLIMIT = 250; 62 63 private static final String PROFILEFILE = Options.getStringProperty("nashorn.profilefile", "NashornProfile.txt"); 64 65 private static final MethodHandle INCREASE_MISS_COUNTER = MH.findStatic(MethodHandles.lookup(), LinkerCallSite.class, "increaseMissCount", MH.type(Object.class, String.class, Object.class)); 66 private static final MethodHandle ON_CATCH_INVALIDATION = MH.findStatic(MethodHandles.lookup(), LinkerCallSite.class, "onCatchInvalidation", MH.type(ChainedCallSite.class, LinkerCallSite.class)); 67 68 private int catchInvalidations; 69 70 LinkerCallSite(final NashornCallSiteDescriptor descriptor) { 71 super(descriptor); 72 if (Context.DEBUG) { 73 LinkerCallSite.count++; 74 } 75 } 76 77 @Override 78 protected MethodHandle getPruneCatches() { 79 return MH.filterArguments(super.getPruneCatches(), 0, ON_CATCH_INVALIDATION); 80 } 81 82 /** 83 * Action to perform when a catch guard around a callsite triggers. Increases 84 * catch invalidation counter 85 * @param callSite callsite 86 * @return the callsite, so this can be used as argument filter 87 */ 88 @SuppressWarnings("unused") 89 private static ChainedCallSite onCatchInvalidation(final LinkerCallSite callSite) { 90 ++callSite.catchInvalidations; 91 return callSite; 92 } 93 94 /** 95 * Get the number of catch invalidations that have happened at this call site so far 96 * @param callSiteToken call site token, unique to the callsite. 97 * @return number of catch invalidations, i.e. thrown exceptions caught by the linker 98 */ 99 public static int getCatchInvalidationCount(final Object callSiteToken) { 100 if (callSiteToken instanceof LinkerCallSite) { 101 return ((LinkerCallSite)callSiteToken).catchInvalidations; 102 } 103 return 0; 104 } 105 /** 106 * Construct a new linker call site. 107 * @param name Name of method. 108 * @param type Method type. 109 * @param flags Call site specific flags. 110 * @return New LinkerCallSite. 111 */ 112 static LinkerCallSite newLinkerCallSite(final MethodHandles.Lookup lookup, final String name, final MethodType type, final int flags) { 113 final NashornCallSiteDescriptor desc = NashornCallSiteDescriptor.get(lookup, name, type, flags); 114 115 if (desc.isProfile()) { 116 return ProfilingLinkerCallSite.newProfilingLinkerCallSite(desc); 117 } 118 119 if (desc.isTrace()) { 120 return new TracingLinkerCallSite(desc); 121 } 122 123 return new LinkerCallSite(desc); 124 } 125 126 @Override 127 public String toString() { 128 return getDescriptor().toString(); 129 } 130 131 /** 132 * Get the descriptor for this callsite 133 * @return a {@link NashornCallSiteDescriptor} 134 */ 135 public NashornCallSiteDescriptor getNashornDescriptor() { 136 return (NashornCallSiteDescriptor)getDescriptor(); 137 } 138 139 @Override 140 public void relink(final GuardedInvocation invocation, final MethodHandle relink) { 141 super.relink(invocation, getDebuggingRelink(relink)); 142 } 143 144 @Override 145 public void resetAndRelink(final GuardedInvocation invocation, final MethodHandle relink) { 146 super.resetAndRelink(invocation, getDebuggingRelink(relink)); 147 } 148 149 private MethodHandle getDebuggingRelink(final MethodHandle relink) { 150 if (Context.DEBUG) { 151 return MH.filterArguments(relink, 0, getIncreaseMissCounter(relink.type().parameterType(0))); 152 } 153 return relink; 154 } 155 156 private MethodHandle getIncreaseMissCounter(final Class<?> type) { 157 final MethodHandle missCounterWithDesc = MH.bindTo(INCREASE_MISS_COUNTER, getDescriptor().getName() + " @ " + getScriptLocation()); 158 if (type == Object.class) { 159 return missCounterWithDesc; 160 } 161 return MH.asType(missCounterWithDesc, missCounterWithDesc.type().changeParameterType(0, type).changeReturnType(type)); 162 } 163 164 private static String getScriptLocation() { 165 final StackTraceElement caller = DynamicLinker.getLinkedCallSiteLocation(); 166 return caller == null ? "unknown location" : (caller.getFileName() + ":" + caller.getLineNumber()); 167 } 168 169 /** 170 * Instrumentation - increase the miss count when a callsite misses. Used as filter 171 * @param desc descriptor for table entry 172 * @param self self reference 173 * @return self reference 174 */ 175 public static Object increaseMissCount(final String desc, final Object self) { 176 ++missCount; 177 if (r.nextInt(100) < missSamplingPercentage) { 178 final AtomicInteger i = missCounts.get(desc); 179 if (i == null) { 180 missCounts.put(desc, new AtomicInteger(1)); 181 } else { 182 i.incrementAndGet(); 183 } 184 } 185 return self; 186 } 187 188 /* 189 * Debugging call sites. 190 */ 191 192 private static class ProfilingLinkerCallSite extends LinkerCallSite { 193 /** List of all profiled call sites. */ 194 private static LinkedList<ProfilingLinkerCallSite> profileCallSites = null; 195 196 /** Start time when entered at zero depth. */ 197 private long startTime; 198 199 /** Depth of nested calls. */ 200 private int depth; 201 202 /** Total time spent in this call site. */ 203 private long totalTime; 204 205 /** Total number of times call site entered. */ 206 private long hitCount; 207 208 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 209 210 private static final MethodHandle PROFILEENTRY = MH.findVirtual(LOOKUP, ProfilingLinkerCallSite.class, "profileEntry", MH.type(Object.class, Object.class)); 211 private static final MethodHandle PROFILEEXIT = MH.findVirtual(LOOKUP, ProfilingLinkerCallSite.class, "profileExit", MH.type(Object.class, Object.class)); 212 private static final MethodHandle PROFILEVOIDEXIT = MH.findVirtual(LOOKUP, ProfilingLinkerCallSite.class, "profileVoidExit", MH.type(void.class)); 213 214 /* 215 * Constructor 216 */ 217 218 ProfilingLinkerCallSite(final NashornCallSiteDescriptor desc) { 219 super(desc); 220 } 221 222 public static ProfilingLinkerCallSite newProfilingLinkerCallSite(final NashornCallSiteDescriptor desc) { 223 if (profileCallSites == null) { 224 profileCallSites = new LinkedList<>(); 225 226 final Thread profileDumperThread = new Thread(new ProfileDumper()); 227 Runtime.getRuntime().addShutdownHook(profileDumperThread); 228 } 229 230 final ProfilingLinkerCallSite callSite = new ProfilingLinkerCallSite(desc); 231 profileCallSites.add(callSite); 232 233 return callSite; 234 } 235 236 @Override 237 public void setTarget(final MethodHandle newTarget) { 238 final MethodType type = type(); 239 final boolean isVoid = type.returnType() == void.class; 240 241 MethodHandle methodHandle = MH.filterArguments(newTarget, 0, MH.bindTo(PROFILEENTRY, this)); 242 243 if (isVoid) { 244 methodHandle = MH.filterReturnValue(methodHandle, MH.bindTo(PROFILEVOIDEXIT, this)); 245 } else { 246 final MethodType filter = MH.type(type.returnType(), type.returnType()); 247 methodHandle = MH.filterReturnValue(methodHandle, MH.asType(MH.bindTo(PROFILEEXIT, this), filter)); 248 } 249 250 super.setTarget(methodHandle); 251 } 252 253 /** 254 * Start the clock for a profile entry and increase depth 255 * @param self argument to filter 256 * @return preserved argument 257 */ 258 @SuppressWarnings("unused") 259 public Object profileEntry(final Object self) { 260 if (depth == 0) { 261 startTime = System.nanoTime(); 262 } 263 264 depth++; 265 hitCount++; 266 267 return self; 268 } 269 270 /** 271 * Decrease depth and stop the clock for a profile entry 272 * @param result return value to filter 273 * @return preserved argument 274 */ 275 @SuppressWarnings("unused") 276 public Object profileExit(final Object result) { 277 depth--; 278 279 if (depth == 0) { 280 totalTime += System.nanoTime() - startTime; 281 } 282 283 return result; 284 } 285 286 /** 287 * Decrease depth without return value filter 288 */ 289 @SuppressWarnings("unused") 290 public void profileVoidExit() { 291 depth--; 292 293 if (depth == 0) { 294 totalTime += System.nanoTime() - startTime; 295 } 296 } 297 298 static class ProfileDumper implements Runnable { 299 @Override 300 public void run() { 301 PrintWriter out = null; 302 boolean fileOutput = false; 303 304 try { 305 try { 306 out = new PrintWriter(new FileOutputStream(PROFILEFILE)); 307 fileOutput = true; 308 } catch (final FileNotFoundException e) { 309 out = Context.getCurrentErr(); 310 } 311 312 dump(out); 313 } finally { 314 if (out != null && fileOutput) { 315 out.close(); 316 } 317 } 318 } 319 320 private static void dump(final PrintWriter out) { 321 int index = 0; 322 for (final ProfilingLinkerCallSite callSite : profileCallSites) { 323 out.println("" + (index++) + '\t' + 324 callSite.getDescriptor().getName() + '\t' + 325 callSite.totalTime + '\t' + 326 callSite.hitCount); 327 } 328 } 329 } 330 } 331 332 /** 333 * Debug subclass for LinkerCallSite that allows tracing 334 */ 335 private static class TracingLinkerCallSite extends LinkerCallSite { 336 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 337 338 private static final MethodHandle TRACEOBJECT = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceObject", MH.type(Object.class, MethodHandle.class, Object[].class)); 339 private static final MethodHandle TRACEVOID = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceVoid", MH.type(void.class, MethodHandle.class, Object[].class)); 340 private static final MethodHandle TRACEMISS = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceMiss", MH.type(void.class, String.class, Object[].class)); 341 342 TracingLinkerCallSite(final NashornCallSiteDescriptor desc) { 343 super(desc); 344 } 345 346 @Override 347 public void setTarget(final MethodHandle newTarget) { 348 if (!getNashornDescriptor().isTraceEnterExit()) { 349 super.setTarget(newTarget); 350 return; 351 } 352 353 final MethodType type = type(); 354 final boolean isVoid = type.returnType() == void.class; 355 356 MethodHandle traceMethodHandle = isVoid ? TRACEVOID : TRACEOBJECT; 357 traceMethodHandle = MH.bindTo(traceMethodHandle, this); 358 traceMethodHandle = MH.bindTo(traceMethodHandle, newTarget); 359 traceMethodHandle = MH.asCollector(traceMethodHandle, Object[].class, type.parameterCount()); 360 traceMethodHandle = MH.asType(traceMethodHandle, type); 361 362 super.setTarget(traceMethodHandle); 363 } 364 365 @Override 366 public void initialize(final MethodHandle relinkAndInvoke) { 367 super.initialize(getFallbackLoggingRelink(relinkAndInvoke)); 368 } 369 370 @Override 371 public void relink(final GuardedInvocation invocation, final MethodHandle relink) { 372 super.relink(invocation, getFallbackLoggingRelink(relink)); 373 } 374 375 @Override 376 public void resetAndRelink(final GuardedInvocation invocation, final MethodHandle relink) { 377 super.resetAndRelink(invocation, getFallbackLoggingRelink(relink)); 378 } 379 380 private MethodHandle getFallbackLoggingRelink(final MethodHandle relink) { 381 if (!getNashornDescriptor().isTraceMisses()) { 382 // If we aren't tracing misses, just return relink as-is 383 return relink; 384 } 385 final MethodType type = relink.type(); 386 return MH.foldArguments(relink, MH.asType(MH.asCollector(MH.insertArguments(TRACEMISS, 0, this, "MISS " + getScriptLocation() + " "), Object[].class, type.parameterCount()), type.changeReturnType(void.class))); 387 } 388 389 private void printObject(final PrintWriter out, final Object arg) { 390 if (!getNashornDescriptor().isTraceObjects()) { 391 out.print((arg instanceof ScriptObject) ? "ScriptObject" : arg); 392 return; 393 } 394 395 if (arg instanceof ScriptObject) { 396 final ScriptObject object = (ScriptObject)arg; 397 398 boolean isFirst = true; 399 final Set<Object> keySet = object.keySet(); 400 401 if (keySet.isEmpty()) { 402 out.print(ScriptRuntime.safeToString(arg)); 403 } else { 404 out.print("{ "); 405 406 for (final Object key : keySet) { 407 if (!isFirst) { 408 out.print(", "); 409 } 410 411 out.print(key); 412 out.print(":"); 413 414 final Object value = object.get(key); 415 416 if (value instanceof ScriptObject) { 417 out.print("..."); 418 } else { 419 printObject(out, value); 420 } 421 422 isFirst = false; 423 } 424 425 out.print(" }"); 426 } 427 } else { 428 out.print(ScriptRuntime.safeToString(arg)); 429 } 430 } 431 432 private void tracePrint(final PrintWriter out, final String tag, final Object[] args, final Object result) { 433 //boolean isVoid = type().returnType() == void.class; 434 out.print(Debug.id(this) + " TAG " + tag); 435 out.print(getDescriptor().getName() + "("); 436 437 if (args.length > 0) { 438 printObject(out, args[0]); 439 for (int i = 1; i < args.length; i++) { 440 final Object arg = args[i]; 441 out.print(", "); 442 443 if (!(arg instanceof ScriptObject && ((ScriptObject)arg).isScope())) { 444 printObject(out, arg); 445 } else { 446 out.print("SCOPE"); 447 } 448 } 449 } 450 451 out.print(")"); 452 453 if (tag.equals("EXIT ")) { 454 out.print(" --> "); 455 printObject(out, result); 456 } 457 458 out.println(); 459 } 460 461 /** 462 * Trace event. Wrap an invocation with a return value 463 * 464 * @param mh invocation handle 465 * @param args arguments to call 466 * 467 * @return return value from invocation 468 * 469 * @throws Throwable if invocation fails or throws exception/error 470 */ 471 @SuppressWarnings("unused") 472 public Object traceObject(final MethodHandle mh, final Object... args) throws Throwable { 473 final PrintWriter out = Context.getCurrentErr(); 474 tracePrint(out, "ENTER ", args, null); 475 final Object result = mh.invokeWithArguments(args); 476 tracePrint(out, "EXIT ", args, result); 477 478 return result; 479 } 480 481 /** 482 * Trace event. Wrap an invocation that returns void 483 * 484 * @param mh invocation handle 485 * @param args arguments to call 486 * 487 * @throws Throwable if invocation fails or throws exception/error 488 */ 489 @SuppressWarnings("unused") 490 public void traceVoid(final MethodHandle mh, final Object... args) throws Throwable { 491 final PrintWriter out = Context.getCurrentErr(); 492 tracePrint(out, "ENTER ", args, null); 493 mh.invokeWithArguments(args); 494 tracePrint(out, "EXIT ", args, null); 495 } 496 497 /** 498 * Tracer function that logs a callsite miss 499 * 500 * @param desc callsite descriptor string 501 * @param args arguments to function 502 * 503 * @throws Throwable if invocation failes or throws exception/error 504 */ 505 @SuppressWarnings("unused") 506 public void traceMiss(final String desc, final Object... args) throws Throwable { 507 tracePrint(Context.getCurrentErr(), desc, args, null); 508 } 509 } 510 511 // counters updated in debug mode 512 private static int count; 513 private static final HashMap<String, AtomicInteger> missCounts = new HashMap<>(); 514 private static int missCount; 515 private static final Random r = new Random(); 516 private static final int missSamplingPercentage = Options.getIntProperty("nashorn.tcs.miss.samplePercent", 1); 517 518 @Override 519 protected int getMaxChainLength() { 520 return 8; 521 } 522 523 /** 524 * Get the callsite count 525 * @return the count 526 */ 527 public static int getCount() { 528 return count; 529 } 530 531 /** 532 * Get the callsite miss count 533 * @return the missCount 534 */ 535 public static int getMissCount() { 536 return missCount; 537 } 538 539 /** 540 * Get given miss sampling percentage for sampler. Default is 1%. Specified with -Dnashorn.tcs.miss.samplePercent=x 541 * @return miss sampling percentage 542 */ 543 public static int getMissSamplingPercentage() { 544 return missSamplingPercentage; 545 } 546 547 /** 548 * Dump the miss counts collected so far to a given output stream 549 * @param out print stream 550 */ 551 public static void getMissCounts(final PrintWriter out) { 552 final ArrayList<Entry<String, AtomicInteger>> entries = new ArrayList<>(missCounts.entrySet()); 553 554 Collections.sort(entries, new Comparator<Map.Entry<String, AtomicInteger>>() { 555 @Override 556 public int compare(final Entry<String, AtomicInteger> o1, final Entry<String, AtomicInteger> o2) { 557 return o2.getValue().get() - o1.getValue().get(); 558 } 559 }); 560 561 for (final Entry<String, AtomicInteger> entry : entries) { 562 out.println(" " + entry.getKey() + "\t" + entry.getValue().get()); 563 } 564 } 565 566} 567