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