001/*
002 * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.truffle;
024
025import static com.oracle.graal.truffle.TruffleCompilerOptions.*;
026
027import java.io.*;
028import java.lang.reflect.*;
029import java.util.*;
030import java.util.concurrent.atomic.*;
031import java.util.stream.*;
032
033import jdk.internal.jvmci.code.*;
034import jdk.internal.jvmci.common.*;
035import jdk.internal.jvmci.meta.*;
036
037import com.oracle.graal.truffle.debug.*;
038import com.oracle.truffle.api.*;
039import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
040import com.oracle.truffle.api.frame.*;
041import com.oracle.truffle.api.impl.*;
042import com.oracle.truffle.api.nodes.*;
043import com.oracle.truffle.api.nodes.Node;
044import com.oracle.truffle.api.utilities.*;
045
046/**
047 * Call target that is optimized by Graal upon surpassing a specific invocation threshold.
048 */
049public class OptimizedCallTarget extends InstalledCode implements RootCallTarget, LoopCountReceiver, ReplaceObserver {
050
051    protected final GraalTruffleRuntime runtime;
052    private SpeculationLog speculationLog;
053    protected final CompilationProfile compilationProfile;
054    protected final CompilationPolicy compilationPolicy;
055    private final OptimizedCallTarget sourceCallTarget;
056    private final AtomicInteger callSitesKnown = new AtomicInteger(0);
057    private final ValueProfile exceptionProfile = ValueProfile.createClassProfile();
058
059    @CompilationFinal private Class<?>[] profiledArgumentTypes;
060    @CompilationFinal private Assumption profiledArgumentTypesAssumption;
061    @CompilationFinal private Class<?> profiledReturnType;
062    @CompilationFinal private Assumption profiledReturnTypeAssumption;
063
064    private final RootNode uninitializedRootNode;
065    private final RootNode rootNode;
066
067    /* Experimental fields for new splitting. */
068    private final Map<TruffleStamp, OptimizedCallTarget> splitVersions = new HashMap<>();
069    private TruffleStamp argumentStamp = DefaultTruffleStamp.getInstance();
070
071    private TruffleInlining inlining;
072    private int cachedNonTrivialNodeCount = -1;
073    private boolean compiling;
074
075    /**
076     * When this call target is inlined, the inlining {@link InstalledCode} registers this
077     * assumption. It gets invalidated when a node rewriting is performed. This ensures that all
078     * compiled methods that have this call target inlined are properly invalidated.
079     */
080    private final CyclicAssumption nodeRewritingAssumption;
081
082    public final RootNode getRootNode() {
083        return rootNode;
084    }
085
086    public OptimizedCallTarget(OptimizedCallTarget sourceCallTarget, RootNode rootNode, GraalTruffleRuntime runtime, CompilationPolicy compilationPolicy, SpeculationLog speculationLog) {
087        super(rootNode.toString());
088        this.sourceCallTarget = sourceCallTarget;
089        this.runtime = runtime;
090        this.speculationLog = speculationLog;
091        this.rootNode = rootNode;
092        this.compilationPolicy = compilationPolicy;
093        this.rootNode.adoptChildren();
094        this.rootNode.applyInstrumentation();
095        this.uninitializedRootNode = sourceCallTarget == null ? cloneRootNode(rootNode) : sourceCallTarget.uninitializedRootNode;
096        if (TruffleCallTargetProfiling.getValue()) {
097            this.compilationProfile = new TraceCompilationProfile();
098        } else {
099            this.compilationProfile = new CompilationProfile();
100        }
101        this.nodeRewritingAssumption = new CyclicAssumption("nodeRewritingAssumption of " + rootNode.toString());
102    }
103
104    public final void log(String message) {
105        runtime.log(message);
106    }
107
108    public final boolean isCompiling() {
109        return compiling;
110    }
111
112    private static RootNode cloneRootNode(RootNode root) {
113        if (root == null || !root.isCloningAllowed()) {
114            return null;
115        }
116        return NodeUtil.cloneNode(root);
117    }
118
119    public Assumption getNodeRewritingAssumption() {
120        return nodeRewritingAssumption.getAssumption();
121    }
122
123    public final void mergeArgumentStamp(TruffleStamp p) {
124        this.argumentStamp = this.argumentStamp.join(p);
125    }
126
127    public final TruffleStamp getArgumentStamp() {
128        return argumentStamp;
129    }
130
131    private int cloneIndex;
132
133    public int getCloneIndex() {
134        return cloneIndex;
135    }
136
137    public OptimizedCallTarget cloneUninitialized() {
138        RootNode copiedRoot = cloneRootNode(uninitializedRootNode);
139        if (copiedRoot == null) {
140            return null;
141        }
142        OptimizedCallTarget splitTarget = (OptimizedCallTarget) runtime.createClonedCallTarget(this, copiedRoot);
143        splitTarget.cloneIndex = cloneIndex++;
144        return splitTarget;
145    }
146
147    public Map<TruffleStamp, OptimizedCallTarget> getSplitVersions() {
148        return splitVersions;
149    }
150
151    public SpeculationLog getSpeculationLog() {
152        return speculationLog;
153    }
154
155    @Override
156    public Object call(Object... args) {
157        compilationProfile.reportIndirectCall();
158        if (profiledArgumentTypesAssumption != null && profiledArgumentTypesAssumption.isValid()) {
159            CompilerDirectives.transferToInterpreterAndInvalidate();
160            profiledArgumentTypesAssumption.invalidate();
161            profiledArgumentTypes = null;
162        }
163        return doInvoke(args);
164    }
165
166    public final Object callDirect(Object... args) {
167        compilationProfile.reportDirectCall();
168        profileArguments(args);
169        try {
170            Object result = doInvoke(args);
171            Class<?> klass = profiledReturnType;
172            if (klass != null && CompilerDirectives.inCompiledCode() && profiledReturnTypeAssumption.isValid()) {
173                result = FrameWithoutBoxing.unsafeCast(result, klass, true, true);
174            }
175            return result;
176        } catch (Throwable t) {
177            t = exceptionProfile.profile(t);
178            if (t instanceof RuntimeException) {
179                throw (RuntimeException) t;
180            } else if (t instanceof Error) {
181                throw (Error) t;
182            } else {
183                CompilerDirectives.transferToInterpreter();
184                throw new RuntimeException(t);
185            }
186        }
187    }
188
189    public final Object callInlined(Object... arguments) {
190        compilationProfile.reportInlinedCall();
191        VirtualFrame frame = createFrame(getRootNode().getFrameDescriptor(), arguments);
192        return callProxy(frame);
193    }
194
195    @ExplodeLoop
196    void profileArguments(Object[] args) {
197        if (profiledArgumentTypesAssumption == null) {
198            CompilerDirectives.transferToInterpreterAndInvalidate();
199            initializeProfiledArgumentTypes(args);
200        } else if (profiledArgumentTypes != null) {
201            if (profiledArgumentTypes.length != args.length) {
202                CompilerDirectives.transferToInterpreterAndInvalidate();
203                profiledArgumentTypesAssumption.invalidate();
204                profiledArgumentTypes = null;
205            } else if (TruffleArgumentTypeSpeculation.getValue() && profiledArgumentTypesAssumption.isValid()) {
206                for (int i = 0; i < profiledArgumentTypes.length; i++) {
207                    if (profiledArgumentTypes[i] != null && !profiledArgumentTypes[i].isInstance(args[i])) {
208                        CompilerDirectives.transferToInterpreterAndInvalidate();
209                        updateProfiledArgumentTypes(args);
210                        break;
211                    }
212                }
213            }
214        }
215    }
216
217    private void initializeProfiledArgumentTypes(Object[] args) {
218        CompilerAsserts.neverPartOfCompilation();
219        profiledArgumentTypesAssumption = Truffle.getRuntime().createAssumption("Profiled Argument Types");
220        profiledArgumentTypes = new Class<?>[args.length];
221        if (TruffleArgumentTypeSpeculation.getValue()) {
222            for (int i = 0; i < args.length; i++) {
223                profiledArgumentTypes[i] = classOf(args[i]);
224            }
225        }
226    }
227
228    private void updateProfiledArgumentTypes(Object[] args) {
229        CompilerAsserts.neverPartOfCompilation();
230        profiledArgumentTypesAssumption.invalidate();
231        for (int j = 0; j < profiledArgumentTypes.length; j++) {
232            profiledArgumentTypes[j] = joinTypes(profiledArgumentTypes[j], classOf(args[j]));
233        }
234        profiledArgumentTypesAssumption = Truffle.getRuntime().createAssumption("Profiled Argument Types");
235    }
236
237    private static Class<?> classOf(Object arg) {
238        return arg != null ? arg.getClass() : null;
239    }
240
241    private static Class<?> joinTypes(Class<?> class1, Class<?> class2) {
242        if (class1 == class2) {
243            return class1;
244        } else if (class1 == null || class2 == null) {
245            return null;
246        } else if (class1.isAssignableFrom(class2)) {
247            return class1;
248        } else if (class2.isAssignableFrom(class1)) {
249            return class2;
250        } else {
251            return Object.class;
252        }
253    }
254
255    protected Object doInvoke(Object[] args) {
256        return callBoundary(args);
257    }
258
259    @TruffleCallBoundary
260    protected final Object callBoundary(Object[] args) {
261        if (CompilerDirectives.inInterpreter()) {
262            // We are called and we are still in Truffle interpreter mode.
263            interpreterCall();
264        } else {
265            // We come here from compiled code
266        }
267
268        return callRoot(args);
269    }
270
271    public final Object callRoot(Object[] originalArguments) {
272        Object[] args = originalArguments;
273        if (this.profiledArgumentTypesAssumption != null && CompilerDirectives.inCompiledCode() && profiledArgumentTypesAssumption.isValid()) {
274            args = FrameWithoutBoxing.unsafeCast(castArrayFixedLength(args, profiledArgumentTypes.length), Object[].class, true, true);
275            if (TruffleArgumentTypeSpeculation.getValue()) {
276                args = castArguments(args);
277            }
278        }
279
280        VirtualFrame frame = createFrame(getRootNode().getFrameDescriptor(), args);
281        Object result = callProxy(frame);
282
283        profileReturnType(result);
284
285        return result;
286    }
287
288    void profileReturnType(Object result) {
289        if (profiledReturnTypeAssumption == null) {
290            if (TruffleReturnTypeSpeculation.getValue()) {
291                CompilerDirectives.transferToInterpreterAndInvalidate();
292                profiledReturnType = (result == null ? null : result.getClass());
293                profiledReturnTypeAssumption = Truffle.getRuntime().createAssumption("Profiled Return Type");
294            }
295        } else if (profiledReturnType != null) {
296            if (result == null || profiledReturnType != result.getClass()) {
297                CompilerDirectives.transferToInterpreterAndInvalidate();
298                profiledReturnType = null;
299                profiledReturnTypeAssumption.invalidate();
300            }
301        }
302    }
303
304    @Override
305    public void invalidate() {
306        invalidate(null, null);
307    }
308
309    protected void invalidate(Object source, CharSequence reason) {
310        if (isValid()) {
311            this.runtime.invalidateInstalledCode(this, source, reason);
312        }
313        cachedNonTrivialNodeCount = -1;
314    }
315
316    public TruffleInlining getInlining() {
317        return inlining;
318    }
319
320    public void setInlining(TruffleInlining inliningDecision) {
321        this.inlining = inliningDecision;
322    }
323
324    private boolean cancelInstalledTask(Node source, CharSequence reason) {
325        return this.runtime.cancelInstalledTask(this, source, reason);
326    }
327
328    private void interpreterCall() {
329        if (isValid()) {
330            // Stubs were deoptimized => reinstall.
331            this.runtime.reinstallStubs();
332        } else {
333            compilationProfile.reportInterpreterCall();
334            if (!isCompiling() && compilationPolicy.shouldCompile(compilationProfile, getCompilerOptions())) {
335                compile();
336            }
337        }
338    }
339
340    public void compile() {
341        if (!isCompiling()) {
342            compiling = true;
343            runtime.compile(this, TruffleBackgroundCompilation.getValue() && !TruffleCompilationExceptionsAreThrown.getValue());
344        }
345    }
346
347    public void notifyCompilationFailed(Throwable t) {
348        if (t instanceof BailoutException && !((BailoutException) t).isPermanent()) {
349            /*
350             * Non permanent bailouts are expected cases. A non permanent bailout would be for
351             * example class redefinition during code installation. As opposed to permanent
352             * bailouts, non permanent bailouts will trigger recompilation and are not considered a
353             * failure state.
354             */
355        } else {
356            compilationPolicy.recordCompilationFailure(t);
357            if (TruffleCompilationExceptionsAreThrown.getValue()) {
358                throw new OptimizationFailedException(t, this);
359            }
360            if (TruffleCompilationExceptionsAreFatal.getValue()) {
361                printException(t);
362                System.exit(-1);
363            }
364        }
365    }
366
367    private void printException(Throwable e) {
368        StringWriter string = new StringWriter();
369        e.printStackTrace(new PrintWriter(string));
370        log(string.toString());
371    }
372
373    public void notifyCompilationFinished(boolean successful) {
374        if (successful && inlining != null) {
375            dequeueInlinedCallSites(inlining);
376        }
377        compiling = false;
378    }
379
380    private void dequeueInlinedCallSites(TruffleInlining parentDecision) {
381        for (TruffleInliningDecision decision : parentDecision) {
382            if (decision.isInline()) {
383                OptimizedCallTarget target = decision.getTarget();
384                target.cancelInstalledTask(decision.getProfile().getCallNode(), "Inlining caller compiled.");
385                dequeueInlinedCallSites(decision);
386            }
387        }
388    }
389
390    protected final Object callProxy(VirtualFrame frame) {
391        try {
392            return getRootNode().execute(frame);
393        } finally {
394            // this assertion is needed to keep the values from being cleared as non-live locals
395            assert frame != null && this != null;
396        }
397    }
398
399    public final int getKnownCallSiteCount() {
400        return callSitesKnown.get();
401    }
402
403    public final void incrementKnownCallSites() {
404        callSitesKnown.incrementAndGet();
405    }
406
407    public final void decrementKnownCallSites() {
408        callSitesKnown.decrementAndGet();
409    }
410
411    public final OptimizedCallTarget getSourceCallTarget() {
412        return sourceCallTarget;
413    }
414
415    @Override
416    public String toString() {
417        String superString = rootNode.toString();
418        if (isValid()) {
419            superString += " <opt>";
420        }
421        if (sourceCallTarget != null) {
422            superString += " <split-" + cloneIndex + "-" + argumentStamp.toStringShort() + ">";
423        }
424        return superString;
425    }
426
427    public CompilationProfile getCompilationProfile() {
428        return compilationProfile;
429    }
430
431    @ExplodeLoop
432    private Object[] castArguments(Object[] originalArguments) {
433        Object[] castArguments = new Object[profiledArgumentTypes.length];
434        for (int i = 0; i < profiledArgumentTypes.length; i++) {
435            castArguments[i] = profiledArgumentTypes[i] != null ? FrameWithoutBoxing.unsafeCast(originalArguments[i], profiledArgumentTypes[i], true, true) : originalArguments[i];
436        }
437        return castArguments;
438    }
439
440    private static Object castArrayFixedLength(Object[] args, @SuppressWarnings("unused") int length) {
441        return args;
442    }
443
444    public static VirtualFrame createFrame(FrameDescriptor descriptor, Object[] args) {
445        if (TruffleCompilerOptions.TruffleUseFrameWithoutBoxing.getValue()) {
446            return new FrameWithoutBoxing(descriptor, args);
447        } else {
448            return new FrameWithBoxing(descriptor, args);
449        }
450    }
451
452    public List<OptimizedDirectCallNode> getCallNodes() {
453        final List<OptimizedDirectCallNode> callNodes = new ArrayList<>();
454        getRootNode().accept(new NodeVisitor() {
455            public boolean visit(Node node) {
456                if (node instanceof OptimizedDirectCallNode) {
457                    callNodes.add((OptimizedDirectCallNode) node);
458                }
459                return true;
460            }
461        });
462        return callNodes;
463    }
464
465    @Override
466    public void reportLoopCount(int count) {
467        compilationProfile.reportLoopCount(count);
468    }
469
470    @Override
471    public boolean nodeReplaced(Node oldNode, Node newNode, CharSequence reason) {
472        CompilerAsserts.neverPartOfCompilation();
473        if (isValid()) {
474            invalidate(newNode, reason);
475        }
476        /* Notify compiled method that have inlined this call target that the tree changed. */
477        nodeRewritingAssumption.invalidate();
478
479        compilationProfile.reportNodeReplaced();
480        if (cancelInstalledTask(newNode, reason)) {
481            compilationProfile.reportInvalidated();
482        }
483        return false;
484    }
485
486    public void accept(NodeVisitor visitor, boolean includeInlinedNodes) {
487        TruffleInlining inliner = getInlining();
488        if (includeInlinedNodes && inliner != null) {
489            inlining.accept(this, visitor);
490        } else {
491            getRootNode().accept(visitor);
492        }
493    }
494
495    public Stream<Node> nodeStream(boolean includeInlinedNodes) {
496        Iterator<Node> iterator;
497        TruffleInlining inliner = getInlining();
498        if (includeInlinedNodes && inliner != null) {
499            iterator = inliner.makeNodeIterator(this);
500        } else {
501            iterator = NodeUtil.makeRecursiveIterator(this.getRootNode());
502        }
503        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
504    }
505
506    public final int getNonTrivialNodeCount() {
507        if (cachedNonTrivialNodeCount == -1) {
508            cachedNonTrivialNodeCount = calculateNonTrivialNodes(getRootNode());
509        }
510        return cachedNonTrivialNodeCount;
511    }
512
513    public static int calculateNonTrivialNodes(Node node) {
514        NonTrivialNodeCountVisitor visitor = new NonTrivialNodeCountVisitor();
515        node.accept(visitor);
516        return visitor.nodeCount;
517    }
518
519    public Map<String, Object> getDebugProperties() {
520        Map<String, Object> properties = new LinkedHashMap<>();
521        AbstractDebugCompilationListener.addASTSizeProperty(this, properties);
522        properties.putAll(getCompilationProfile().getDebugProperties());
523        return properties;
524    }
525
526    public static Method getCallDirectMethod() {
527        try {
528            return OptimizedCallTarget.class.getDeclaredMethod("callDirect", Object[].class);
529        } catch (NoSuchMethodException | SecurityException e) {
530            throw new JVMCIError(e);
531        }
532    }
533
534    public static Method getCallInlinedMethod() {
535        try {
536            return OptimizedCallTarget.class.getDeclaredMethod("callInlined", Object[].class);
537        } catch (NoSuchMethodException | SecurityException e) {
538            throw new JVMCIError(e);
539        }
540    }
541
542    private CompilerOptions getCompilerOptions() {
543        final ExecutionContext context = rootNode.getExecutionContext();
544
545        if (context == null) {
546            return DefaultCompilerOptions.INSTANCE;
547        }
548
549        return context.getCompilerOptions();
550    }
551
552    private static final class NonTrivialNodeCountVisitor implements NodeVisitor {
553
554        public int nodeCount;
555
556        public boolean visit(Node node) {
557            if (!node.getCost().isTrivial()) {
558                nodeCount++;
559            }
560            return true;
561        }
562
563    }
564
565}