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