/*
 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.runtime.linker;

import static jdk.nashorn.internal.lookup.Lookup.MH;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import jdk.dynalink.DynamicLinker;
import jdk.dynalink.linker.GuardedInvocation;
import jdk.dynalink.support.ChainedCallSite;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.Debug;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.options.Options;


Relinkable form of call site.
/** * Relinkable form of call site. */
public class LinkerCallSite extends ChainedCallSite {
Maximum number of arguments passed directly.
/** Maximum number of arguments passed directly. */
public static final int ARGLIMIT = 125; private static final String PROFILEFILE = Options.getStringProperty("nashorn.profilefile", "NashornProfile.txt"); private static final MethodHandle INCREASE_MISS_COUNTER = MH.findStatic(MethodHandles.lookup(), LinkerCallSite.class, "increaseMissCount", MH.type(Object.class, String.class, Object.class)); LinkerCallSite(final NashornCallSiteDescriptor descriptor) { super(descriptor); if (Context.DEBUG) { LinkerCallSite.count.increment(); } }
Construct a new linker call site.
Params:
  • name – Name of method.
  • type – Method type.
  • flags – Call site specific flags.
Returns:New LinkerCallSite.
/** * Construct a new linker call site. * @param name Name of method. * @param type Method type. * @param flags Call site specific flags. * @return New LinkerCallSite. */
static LinkerCallSite newLinkerCallSite(final MethodHandles.Lookup lookup, final String name, final MethodType type, final int flags) { final NashornCallSiteDescriptor desc = NashornCallSiteDescriptor.get(lookup, name, type, flags); if (desc.isProfile()) { return ProfilingLinkerCallSite.newProfilingLinkerCallSite(desc); } if (desc.isTrace()) { return new TracingLinkerCallSite(desc); } return new LinkerCallSite(desc); } @Override public String toString() { return getDescriptor().toString(); }
Get the descriptor for this callsite
Returns:a NashornCallSiteDescriptor
/** * Get the descriptor for this callsite * @return a {@link NashornCallSiteDescriptor} */
public NashornCallSiteDescriptor getNashornDescriptor() { return (NashornCallSiteDescriptor)getDescriptor(); } @Override public void relink(final GuardedInvocation invocation, final MethodHandle relink) { super.relink(invocation, getDebuggingRelink(relink)); } @Override public void resetAndRelink(final GuardedInvocation invocation, final MethodHandle relink) { super.resetAndRelink(invocation, getDebuggingRelink(relink)); } private MethodHandle getDebuggingRelink(final MethodHandle relink) { if (Context.DEBUG) { return MH.filterArguments(relink, 0, getIncreaseMissCounter(relink.type().parameterType(0))); } return relink; } private MethodHandle getIncreaseMissCounter(final Class<?> type) { final MethodHandle missCounterWithDesc = MH.bindTo(INCREASE_MISS_COUNTER, getDescriptor().getOperation() + " @ " + getScriptLocation()); if (type == Object.class) { return missCounterWithDesc; } return MH.asType(missCounterWithDesc, missCounterWithDesc.type().changeParameterType(0, type).changeReturnType(type)); } private static String getScriptLocation() { final StackTraceElement caller = DynamicLinker.getLinkedCallSiteLocation(); return caller == null ? "unknown location" : (caller.getFileName() + ":" + caller.getLineNumber()); }
Instrumentation - increase the miss count when a callsite misses. Used as filter
Params:
  • desc – descriptor for table entry
  • self – self reference
Returns:self reference
/** * Instrumentation - increase the miss count when a callsite misses. Used as filter * @param desc descriptor for table entry * @param self self reference * @return self reference */
public static Object increaseMissCount(final String desc, final Object self) { missCount.increment(); if (r.nextInt(100) < missSamplingPercentage) { final AtomicInteger i = missCounts.get(desc); if (i == null) { missCounts.put(desc, new AtomicInteger(1)); } else { i.incrementAndGet(); } } return self; } /* * Debugging call sites. */ private static class ProfilingLinkerCallSite extends LinkerCallSite {
List of all profiled call sites.
/** List of all profiled call sites. */
private static LinkedList<ProfilingLinkerCallSite> profileCallSites = null;
Start time when entered at zero depth.
/** Start time when entered at zero depth. */
private long startTime;
Depth of nested calls.
/** Depth of nested calls. */
private int depth;
Total time spent in this call site.
/** Total time spent in this call site. */
private long totalTime;
Total number of times call site entered.
/** Total number of times call site entered. */
private long hitCount; private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final MethodHandle PROFILEENTRY = MH.findVirtual(LOOKUP, ProfilingLinkerCallSite.class, "profileEntry", MH.type(Object.class, Object.class)); private static final MethodHandle PROFILEEXIT = MH.findVirtual(LOOKUP, ProfilingLinkerCallSite.class, "profileExit", MH.type(Object.class, Object.class)); private static final MethodHandle PROFILEVOIDEXIT = MH.findVirtual(LOOKUP, ProfilingLinkerCallSite.class, "profileVoidExit", MH.type(void.class)); /* * Constructor */ ProfilingLinkerCallSite(final NashornCallSiteDescriptor desc) { super(desc); } public static ProfilingLinkerCallSite newProfilingLinkerCallSite(final NashornCallSiteDescriptor desc) { if (profileCallSites == null) { profileCallSites = new LinkedList<>(); final Thread profileDumperThread = new Thread(new ProfileDumper()); Runtime.getRuntime().addShutdownHook(profileDumperThread); } final ProfilingLinkerCallSite callSite = new ProfilingLinkerCallSite(desc); profileCallSites.add(callSite); return callSite; } @Override public void setTarget(final MethodHandle newTarget) { final MethodType type = type(); final boolean isVoid = type.returnType() == void.class; final Class<?> newSelfType = newTarget.type().parameterType(0); MethodHandle selfFilter = MH.bindTo(PROFILEENTRY, this); if (newSelfType != Object.class) { // new target uses a more precise 'self' type than Object.class. We need to // convert the filter type. Note that the profileEntry method returns "self" // argument "as is" and so the cast introduced will succeed for any type. final MethodType selfFilterType = MethodType.methodType(newSelfType, newSelfType); selfFilter = selfFilter.asType(selfFilterType); } MethodHandle methodHandle = MH.filterArguments(newTarget, 0, selfFilter); if (isVoid) { methodHandle = MH.filterReturnValue(methodHandle, MH.bindTo(PROFILEVOIDEXIT, this)); } else { final MethodType filter = MH.type(type.returnType(), type.returnType()); methodHandle = MH.filterReturnValue(methodHandle, MH.asType(MH.bindTo(PROFILEEXIT, this), filter)); } super.setTarget(methodHandle); }
Start the clock for a profile entry and increase depth
Params:
  • self – argument to filter
Returns:preserved argument
/** * Start the clock for a profile entry and increase depth * @param self argument to filter * @return preserved argument */
@SuppressWarnings("unused") public Object profileEntry(final Object self) { if (depth == 0) { startTime = System.nanoTime(); } depth++; hitCount++; return self; }
Decrease depth and stop the clock for a profile entry
Params:
  • result – return value to filter
Returns:preserved argument
/** * Decrease depth and stop the clock for a profile entry * @param result return value to filter * @return preserved argument */
@SuppressWarnings("unused") public Object profileExit(final Object result) { depth--; if (depth == 0) { totalTime += System.nanoTime() - startTime; } return result; }
Decrease depth without return value filter
/** * Decrease depth without return value filter */
@SuppressWarnings("unused") public void profileVoidExit() { depth--; if (depth == 0) { totalTime += System.nanoTime() - startTime; } } static class ProfileDumper implements Runnable { @Override public void run() { PrintWriter out = null; boolean fileOutput = false; try { try { out = new PrintWriter(new FileOutputStream(PROFILEFILE)); fileOutput = true; } catch (final FileNotFoundException e) { out = Context.getCurrentErr(); } dump(out); } finally { if (out != null && fileOutput) { out.close(); } } } private static void dump(final PrintWriter out) { int index = 0; for (final ProfilingLinkerCallSite callSite : profileCallSites) { out.println("" + (index++) + '\t' + callSite.getDescriptor().getOperation() + '\t' + callSite.totalTime + '\t' + callSite.hitCount); } } } }
Debug subclass for LinkerCallSite that allows tracing
/** * Debug subclass for LinkerCallSite that allows tracing */
private static class TracingLinkerCallSite extends LinkerCallSite { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final MethodHandle TRACEOBJECT = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceObject", MH.type(Object.class, MethodHandle.class, Object[].class)); private static final MethodHandle TRACEVOID = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceVoid", MH.type(void.class, MethodHandle.class, Object[].class)); private static final MethodHandle TRACEMISS = MH.findVirtual(LOOKUP, TracingLinkerCallSite.class, "traceMiss", MH.type(void.class, String.class, Object[].class)); TracingLinkerCallSite(final NashornCallSiteDescriptor desc) { super(desc); } @Override public void setTarget(final MethodHandle newTarget) { if (!getNashornDescriptor().isTraceEnterExit()) { super.setTarget(newTarget); return; } final MethodType type = type(); final boolean isVoid = type.returnType() == void.class; MethodHandle traceMethodHandle = isVoid ? TRACEVOID : TRACEOBJECT; traceMethodHandle = MH.bindTo(traceMethodHandle, this); traceMethodHandle = MH.bindTo(traceMethodHandle, newTarget); traceMethodHandle = MH.asCollector(traceMethodHandle, Object[].class, type.parameterCount()); traceMethodHandle = MH.asType(traceMethodHandle, type); super.setTarget(traceMethodHandle); } @Override public void initialize(final MethodHandle relinkAndInvoke) { super.initialize(getFallbackLoggingRelink(relinkAndInvoke)); } @Override public void relink(final GuardedInvocation invocation, final MethodHandle relink) { super.relink(invocation, getFallbackLoggingRelink(relink)); } @Override public void resetAndRelink(final GuardedInvocation invocation, final MethodHandle relink) { super.resetAndRelink(invocation, getFallbackLoggingRelink(relink)); } private MethodHandle getFallbackLoggingRelink(final MethodHandle relink) { if (!getNashornDescriptor().isTraceMisses()) { // If we aren't tracing misses, just return relink as-is return relink; } final MethodType type = relink.type(); return MH.foldArguments(relink, MH.asType(MH.asCollector(MH.insertArguments(TRACEMISS, 0, this, "MISS " + getScriptLocation() + " "), Object[].class, type.parameterCount()), type.changeReturnType(void.class))); } private void printObject(final PrintWriter out, final Object arg) { if (!getNashornDescriptor().isTraceObjects()) { out.print((arg instanceof ScriptObject) ? "ScriptObject" : arg); return; } if (arg instanceof ScriptObject) { final ScriptObject object = (ScriptObject)arg; boolean isFirst = true; final Set<Object> keySet = object.keySet(); if (keySet.isEmpty()) { out.print(ScriptRuntime.safeToString(arg)); } else { out.print("{ "); for (final Object key : keySet) { if (!isFirst) { out.print(", "); } out.print(key); out.print(":"); final Object value = object.get(key); if (value instanceof ScriptObject) { out.print("..."); } else { printObject(out, value); } isFirst = false; } out.print(" }"); } } else { out.print(ScriptRuntime.safeToString(arg)); } } private void tracePrint(final PrintWriter out, final String tag, final Object[] args, final Object result) { //boolean isVoid = type().returnType() == void.class; out.print(Debug.id(this) + " TAG " + tag); out.print(getDescriptor().getOperation() + "("); if (args.length > 0) { printObject(out, args[0]); for (int i = 1; i < args.length; i++) { final Object arg = args[i]; out.print(", "); if (!(arg instanceof ScriptObject && ((ScriptObject)arg).isScope())) { printObject(out, arg); } else { out.print("SCOPE"); } } } out.print(")"); if (tag.equals("EXIT ")) { out.print(" --> "); printObject(out, result); } out.println(); }
Trace event. Wrap an invocation with a return value
Params:
  • mh – invocation handle
  • args – arguments to call
Throws:
  • Throwable – if invocation fails or throws exception/error
Returns:return value from invocation
/** * Trace event. Wrap an invocation with a return value * * @param mh invocation handle * @param args arguments to call * * @return return value from invocation * * @throws Throwable if invocation fails or throws exception/error */
@SuppressWarnings("unused") public Object traceObject(final MethodHandle mh, final Object... args) throws Throwable { final PrintWriter out = Context.getCurrentErr(); tracePrint(out, "ENTER ", args, null); final Object result = mh.invokeWithArguments(args); tracePrint(out, "EXIT ", args, result); return result; }
Trace event. Wrap an invocation that returns void
Params:
  • mh – invocation handle
  • args – arguments to call
Throws:
  • Throwable – if invocation fails or throws exception/error
/** * Trace event. Wrap an invocation that returns void * * @param mh invocation handle * @param args arguments to call * * @throws Throwable if invocation fails or throws exception/error */
@SuppressWarnings("unused") public void traceVoid(final MethodHandle mh, final Object... args) throws Throwable { final PrintWriter out = Context.getCurrentErr(); tracePrint(out, "ENTER ", args, null); mh.invokeWithArguments(args); tracePrint(out, "EXIT ", args, null); }
Tracer function that logs a callsite miss
Params:
  • desc – callsite descriptor string
  • args – arguments to function
Throws:
  • Throwable – if invocation fails or throws exception/error
/** * Tracer function that logs a callsite miss * * @param desc callsite descriptor string * @param args arguments to function * * @throws Throwable if invocation fails or throws exception/error */
@SuppressWarnings("unused") public void traceMiss(final String desc, final Object... args) throws Throwable { tracePrint(Context.getCurrentErr(), desc, args, null); } } // counters updated in debug mode private static LongAdder count; private static final HashMap<String, AtomicInteger> missCounts = new HashMap<>(); private static LongAdder missCount; private static final Random r = new Random(); private static final int missSamplingPercentage = Options.getIntProperty("nashorn.tcs.miss.samplePercent", 1); static { if (Context.DEBUG) { count = new LongAdder(); missCount = new LongAdder(); } } @Override protected int getMaxChainLength() { return 8; }
Get the callsite count
Returns:the count
/** * Get the callsite count * @return the count */
public static long getCount() { return count.longValue(); }
Get the callsite miss count
Returns:the missCount
/** * Get the callsite miss count * @return the missCount */
public static long getMissCount() { return missCount.longValue(); }
Get given miss sampling percentage for sampler. Default is 1%. Specified with -Dnashorn.tcs.miss.samplePercent=x
Returns:miss sampling percentage
/** * Get given miss sampling percentage for sampler. Default is 1%. Specified with -Dnashorn.tcs.miss.samplePercent=x * @return miss sampling percentage */
public static int getMissSamplingPercentage() { return missSamplingPercentage; }
Dump the miss counts collected so far to a given output stream
Params:
  • out – print stream
/** * Dump the miss counts collected so far to a given output stream * @param out print stream */
public static void getMissCounts(final PrintWriter out) { final ArrayList<Entry<String, AtomicInteger>> entries = new ArrayList<>(missCounts.entrySet()); Collections.sort(entries, new Comparator<Map.Entry<String, AtomicInteger>>() { @Override public int compare(final Entry<String, AtomicInteger> o1, final Entry<String, AtomicInteger> o2) { return o2.getValue().get() - o1.getValue().get(); } }); for (final Entry<String, AtomicInteger> entry : entries) { out.println(" " + entry.getKey() + "\t" + entry.getValue().get()); } } }