LinkerCallSite.java revision 1551:f3b883bec2d0
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 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 211 MethodHandle methodHandle = MH.filterArguments(newTarget, 0, MH.bindTo(PROFILEENTRY, this)); 212 213 if (isVoid) { 214 methodHandle = MH.filterReturnValue(methodHandle, MH.bindTo(PROFILEVOIDEXIT, this)); 215 } else { 216 final MethodType filter = MH.type(type.returnType(), type.returnType()); 217 methodHandle = MH.filterReturnValue(methodHandle, MH.asType(MH.bindTo(PROFILEEXIT, this), filter)); 218 } 219 220 super.setTarget(methodHandle); 221 } 222 223 /** 224 * Start the clock for a profile entry and increase depth 225 * @param self argument to filter 226 * @return preserved argument 227 */ 228 @SuppressWarnings("unused") 229 public Object profileEntry(final Object self) { 230 if (depth == 0) { 231 startTime = System.nanoTime(); 232 } 233 234 depth++; 235 hitCount++; 236 237 return self; 238 } 239 240 /** 241 * Decrease depth and stop the clock for a profile entry 242 * @param result return value to filter 243 * @return preserved argument 244 */ 245 @SuppressWarnings("unused") 246 public Object profileExit(final Object result) { 247 depth--; 248 249 if (depth == 0) { 250 totalTime += System.nanoTime() - startTime; 251 } 252 253 return result; 254 } 255 256 /** 257 * Decrease depth without return value filter 258 */ 259 @SuppressWarnings("unused") 260 public void profileVoidExit() { 261 depth--; 262 263 if (depth == 0) { 264 totalTime += System.nanoTime() - startTime; 265 } 266 } 267 268 static class ProfileDumper implements Runnable { 269 @Override 270 public void run() { 271 PrintWriter out = null; 272 boolean fileOutput = false; 273 274 try { 275 try { 276 out = new PrintWriter(new FileOutputStream(PROFILEFILE)); 277 fileOutput = true; 278 } catch (final FileNotFoundException e) { 279 out = Context.getCurrentErr(); 280 } 281 282 dump(out); 283 } finally { 284 if (out != null && fileOutput) { 285 out.close(); 286 } 287 } 288 } 289 290 private static void dump(final PrintWriter out) { 291 int index = 0; 292 for (final ProfilingLinkerCallSite callSite : profileCallSites) { 293 out.println("" + (index++) + '\t' + 294 callSite.getDescriptor().getOperation() + '\t' + 295 callSite.totalTime + '\t' + 296 callSite.hitCount); 297 } 298 } 299 } 300 } 301 302 /** 303 * Debug subclass for LinkerCallSite that allows tracing 304 */ 305 private static class TracingLinkerCallSite extends LinkerCallSite { 306 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 307 308 private static final MethodHandle TRACEOBJECT = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceObject", MH.type(Object.class, MethodHandle.class, Object[].class)); 309 private static final MethodHandle TRACEVOID = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceVoid", MH.type(void.class, MethodHandle.class, Object[].class)); 310 private static final MethodHandle TRACEMISS = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceMiss", MH.type(void.class, String.class, Object[].class)); 311 312 TracingLinkerCallSite(final NashornCallSiteDescriptor desc) { 313 super(desc); 314 } 315 316 @Override 317 public void setTarget(final MethodHandle newTarget) { 318 if (!getNashornDescriptor().isTraceEnterExit()) { 319 super.setTarget(newTarget); 320 return; 321 } 322 323 final MethodType type = type(); 324 final boolean isVoid = type.returnType() == void.class; 325 326 MethodHandle traceMethodHandle = isVoid ? TRACEVOID : TRACEOBJECT; 327 traceMethodHandle = MH.bindTo(traceMethodHandle, this); 328 traceMethodHandle = MH.bindTo(traceMethodHandle, newTarget); 329 traceMethodHandle = MH.asCollector(traceMethodHandle, Object[].class, type.parameterCount()); 330 traceMethodHandle = MH.asType(traceMethodHandle, type); 331 332 super.setTarget(traceMethodHandle); 333 } 334 335 @Override 336 public void initialize(final MethodHandle relinkAndInvoke) { 337 super.initialize(getFallbackLoggingRelink(relinkAndInvoke)); 338 } 339 340 @Override 341 public void relink(final GuardedInvocation invocation, final MethodHandle relink) { 342 super.relink(invocation, getFallbackLoggingRelink(relink)); 343 } 344 345 @Override 346 public void resetAndRelink(final GuardedInvocation invocation, final MethodHandle relink) { 347 super.resetAndRelink(invocation, getFallbackLoggingRelink(relink)); 348 } 349 350 private MethodHandle getFallbackLoggingRelink(final MethodHandle relink) { 351 if (!getNashornDescriptor().isTraceMisses()) { 352 // If we aren't tracing misses, just return relink as-is 353 return relink; 354 } 355 final MethodType type = relink.type(); 356 return MH.foldArguments(relink, MH.asType(MH.asCollector(MH.insertArguments(TRACEMISS, 0, this, "MISS " + getScriptLocation() + " "), Object[].class, type.parameterCount()), type.changeReturnType(void.class))); 357 } 358 359 private void printObject(final PrintWriter out, final Object arg) { 360 if (!getNashornDescriptor().isTraceObjects()) { 361 out.print((arg instanceof ScriptObject) ? "ScriptObject" : arg); 362 return; 363 } 364 365 if (arg instanceof ScriptObject) { 366 final ScriptObject object = (ScriptObject)arg; 367 368 boolean isFirst = true; 369 final Set<Object> keySet = object.keySet(); 370 371 if (keySet.isEmpty()) { 372 out.print(ScriptRuntime.safeToString(arg)); 373 } else { 374 out.print("{ "); 375 376 for (final Object key : keySet) { 377 if (!isFirst) { 378 out.print(", "); 379 } 380 381 out.print(key); 382 out.print(":"); 383 384 final Object value = object.get(key); 385 386 if (value instanceof ScriptObject) { 387 out.print("..."); 388 } else { 389 printObject(out, value); 390 } 391 392 isFirst = false; 393 } 394 395 out.print(" }"); 396 } 397 } else { 398 out.print(ScriptRuntime.safeToString(arg)); 399 } 400 } 401 402 private void tracePrint(final PrintWriter out, final String tag, final Object[] args, final Object result) { 403 //boolean isVoid = type().returnType() == void.class; 404 out.print(Debug.id(this) + " TAG " + tag); 405 out.print(getDescriptor().getOperation() + "("); 406 407 if (args.length > 0) { 408 printObject(out, args[0]); 409 for (int i = 1; i < args.length; i++) { 410 final Object arg = args[i]; 411 out.print(", "); 412 413 if (!(arg instanceof ScriptObject && ((ScriptObject)arg).isScope())) { 414 printObject(out, arg); 415 } else { 416 out.print("SCOPE"); 417 } 418 } 419 } 420 421 out.print(")"); 422 423 if (tag.equals("EXIT ")) { 424 out.print(" --> "); 425 printObject(out, result); 426 } 427 428 out.println(); 429 } 430 431 /** 432 * Trace event. Wrap an invocation with a return value 433 * 434 * @param mh invocation handle 435 * @param args arguments to call 436 * 437 * @return return value from invocation 438 * 439 * @throws Throwable if invocation fails or throws exception/error 440 */ 441 @SuppressWarnings("unused") 442 public Object traceObject(final MethodHandle mh, final Object... args) throws Throwable { 443 final PrintWriter out = Context.getCurrentErr(); 444 tracePrint(out, "ENTER ", args, null); 445 final Object result = mh.invokeWithArguments(args); 446 tracePrint(out, "EXIT ", args, result); 447 448 return result; 449 } 450 451 /** 452 * Trace event. Wrap an invocation that returns void 453 * 454 * @param mh invocation handle 455 * @param args arguments to call 456 * 457 * @throws Throwable if invocation fails or throws exception/error 458 */ 459 @SuppressWarnings("unused") 460 public void traceVoid(final MethodHandle mh, final Object... args) throws Throwable { 461 final PrintWriter out = Context.getCurrentErr(); 462 tracePrint(out, "ENTER ", args, null); 463 mh.invokeWithArguments(args); 464 tracePrint(out, "EXIT ", args, null); 465 } 466 467 /** 468 * Tracer function that logs a callsite miss 469 * 470 * @param desc callsite descriptor string 471 * @param args arguments to function 472 * 473 * @throws Throwable if invocation fails or throws exception/error 474 */ 475 @SuppressWarnings("unused") 476 public void traceMiss(final String desc, final Object... args) throws Throwable { 477 tracePrint(Context.getCurrentErr(), desc, args, null); 478 } 479 } 480 481 // counters updated in debug mode 482 private static LongAdder count; 483 private static final HashMap<String, AtomicInteger> missCounts = new HashMap<>(); 484 private static LongAdder missCount; 485 private static final Random r = new Random(); 486 private static final int missSamplingPercentage = Options.getIntProperty("nashorn.tcs.miss.samplePercent", 1); 487 488 static { 489 if (Context.DEBUG) { 490 count = new LongAdder(); 491 missCount = new LongAdder(); 492 } 493 } 494 495 @Override 496 protected int getMaxChainLength() { 497 return 8; 498 } 499 500 /** 501 * Get the callsite count 502 * @return the count 503 */ 504 public static long getCount() { 505 return count.longValue(); 506 } 507 508 /** 509 * Get the callsite miss count 510 * @return the missCount 511 */ 512 public static long getMissCount() { 513 return missCount.longValue(); 514 } 515 516 /** 517 * Get given miss sampling percentage for sampler. Default is 1%. Specified with -Dnashorn.tcs.miss.samplePercent=x 518 * @return miss sampling percentage 519 */ 520 public static int getMissSamplingPercentage() { 521 return missSamplingPercentage; 522 } 523 524 /** 525 * Dump the miss counts collected so far to a given output stream 526 * @param out print stream 527 */ 528 public static void getMissCounts(final PrintWriter out) { 529 final ArrayList<Entry<String, AtomicInteger>> entries = new ArrayList<>(missCounts.entrySet()); 530 531 Collections.sort(entries, new Comparator<Map.Entry<String, AtomicInteger>>() { 532 @Override 533 public int compare(final Entry<String, AtomicInteger> o1, final Entry<String, AtomicInteger> o2) { 534 return o2.getValue().get() - o1.getValue().get(); 535 } 536 }); 537 538 for (final Entry<String, AtomicInteger> entry : entries) { 539 out.println(" " + entry.getKey() + "\t" + entry.getValue().get()); 540 } 541 } 542 543} 544