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