changeset 14983:a31d807757ee

Truffle: made inlining fully context sensitive.
author Christian Humer <christian.humer@gmail.com>
date Thu, 03 Apr 2014 19:06:58 +0200
parents 1422f0bd55e3
children a5bebb69dc78
files graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/DefaultInliningPolicy.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallNode.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallNodeProfile.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallTarget.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallTargetImpl.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallUtils.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/PartialEvaluator.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleCallPath.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleCompilerImpl.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleCompilerOptions.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningHandler.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningPolicy.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningProfile.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningResult.java graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleTreeDumpHandler.java
diffstat 15 files changed, 1125 insertions(+), 618 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/DefaultInliningPolicy.java	Thu Apr 03 19:06:58 2014 +0200
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ * 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 com.oracle.graal.truffle;
+
+import static com.oracle.graal.truffle.TruffleCompilerOptions.*;
+
+public class DefaultInliningPolicy implements TruffleInliningPolicy {
+
+    private static final String REASON_RECURSION = "recursion";
+    private static final String REASON_MAXIMUM_NODE_COUNT = "nodeCount * callSites  > " + TruffleInliningMaxCallerSize.getValue();
+    private static final String REASON_MAXIMUM_TOTAL_NODE_COUNT = "totalNodeCount > " + TruffleInliningMaxCallerSize.getValue();
+
+    public double calculateScore(TruffleInliningProfile profile) {
+        return profile.getFrequency() / profile.getDeepNodeCount();
+    }
+
+    public boolean isAllowed(TruffleInliningResult state, TruffleInliningProfile profile, int nodeCountBudget) {
+        TruffleCallPath callPath = profile.getCallPath();
+        OptimizedCallTarget inlineTarget = callPath.getCallTarget();
+        for (TruffleCallPath path : callPath.getParent().toList()) {
+            if (path.getCallTarget() == inlineTarget) {
+                // recursive call found
+                profile.setFailedReason(REASON_RECURSION);
+                return false;
+            }
+        }
+
+        if (profile.isForced()) {
+            return true;
+        }
+
+        if (nodeCountBudget - profile.getDeepNodeCount() < 0) {
+            profile.setFailedReason(REASON_MAXIMUM_TOTAL_NODE_COUNT);
+            return false;
+        }
+
+        int cappedCallSites = Math.min(Math.max(profile.getCallSites(), 1), 10);
+        if (profile.getDeepNodeCount() * cappedCallSites > TruffleInliningMaxCallerSize.getValue()) {
+            profile.setFailedReason(REASON_MAXIMUM_NODE_COUNT);
+            return false;
+        }
+
+        return true;
+    }
+
+    public boolean isAllowedDeep(TruffleInliningResult state, TruffleInliningProfile profile, int nodeCountBudget) {
+        return true;
+    }
+
+}
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallNode.java	Thu Apr 03 18:33:48 2014 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallNode.java	Thu Apr 03 19:06:58 2014 +0200
@@ -22,9 +22,10 @@
  */
 package com.oracle.graal.truffle;
 
-import java.util.*;
+import java.util.concurrent.atomic.*;
 
 import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.api.impl.*;
 import com.oracle.truffle.api.nodes.*;
@@ -33,258 +34,169 @@
 /**
  * Call target that is optimized by Graal upon surpassing a specific invocation threshold.
  */
-public abstract class OptimizedCallNode extends DefaultCallNode {
+public final class OptimizedCallNode extends DefaultCallNode {
 
     protected int callCount;
+    private boolean trySplit = true;
+    private boolean inliningForced;
+    @CompilationFinal private OptimizedCallTarget splitCallTarget;
+    private final AtomicInteger inliningCounter = new AtomicInteger(0);
 
     private OptimizedCallNode(OptimizedCallTarget target) {
         super(target);
     }
 
     @Override
-    public final boolean isSplittable() {
+    public boolean isSplittable() {
         return getCallTarget().getRootNode().isSplittable();
     }
 
     @Override
-    public final OptimizedCallTarget getCallTarget() {
+    public OptimizedCallTarget getCallTarget() {
         return (OptimizedCallTarget) super.getCallTarget();
     }
 
-    public final int getCallCount() {
+    public int getCallCount() {
         return callCount;
     }
 
-    public TruffleInliningProfile createInliningProfile(OptimizedCallTarget target) {
-        return new OptimizedCallNodeProfile(target, this);
-    }
-
-    @SuppressWarnings("unused")
-    public void nodeReplaced(Node oldNode, Node newNode, CharSequence reason) {
-    }
-
     @Override
-    public final OptimizedCallTarget getCurrentCallTarget() {
+    public OptimizedCallTarget getCurrentCallTarget() {
         return (OptimizedCallTarget) super.getCurrentCallTarget();
     }
 
     @Override
     public OptimizedCallTarget getSplitCallTarget() {
-        return null;
-    }
-
-    protected OptimizedCallNode inlineImpl() {
-        if (getParent() == null) {
-            throw new IllegalStateException("CallNode must be adopted before it is split.");
-        }
-
-        return replace(new InlinedOptimizedCallNode(getCallTarget(), getSplitCallTarget(), callCount));
+        return splitCallTarget;
     }
 
     public static OptimizedCallNode create(OptimizedCallTarget target) {
-        return new DefaultOptimizedCallNode(target);
+        return new OptimizedCallNode(target);
     }
 
     @Override
-    public boolean isInlinable() {
-        return true;
+    public Object call(PackedFrame caller, Arguments arguments) {
+        if (CompilerDirectives.inInterpreter()) {
+            interpreterCall();
+            if (inliningCounter.get() > 0 || inliningForced) {
+                return getCurrentCallTarget().callInlined(caller, arguments);
+            }
+        }
+        return getCurrentCallTarget().call(caller, arguments);
     }
 
-    private static final class DefaultOptimizedCallNode extends OptimizedCallNode {
-
-        private boolean trySplit = true;
-
-        DefaultOptimizedCallNode(OptimizedCallTarget target) {
-            super(target);
-            registerCallTarget(this);
-        }
-
-        @Override
-        public Object call(PackedFrame caller, Arguments arguments) {
-            if (CompilerDirectives.inInterpreter()) {
-                callCount++;
-                if (trySplit && callCount > 1) {
-                    trySplit = false;
-                    return trySplit(caller, arguments);
+    private void interpreterCall() {
+        callCount++;
+        if (trySplit) {
+            if (callCount == 1) {
+                // on first call
+                getCurrentCallTarget().incrementKnownCallSite();
+            }
+            if (callCount > 1) {
+                trySplit = false;
+                if (shouldSplit()) {
+                    splitImpl(true);
                 }
             }
-            return callTarget.call(caller, arguments);
         }
+    }
 
-        private Object trySplit(PackedFrame caller, Arguments arguments) {
-            if (shouldSplit()) {
-                return splitImpl(true).call(caller, arguments);
-            }
-            return callTarget.call(caller, arguments);
-        }
+    void notifyInlining() {
+        inliningCounter.incrementAndGet();
+    }
+
+    void notifyInliningDone() {
+        inliningCounter.decrementAndGet();
+    }
 
-        private boolean shouldSplit() {
-            if (!TruffleCompilerOptions.TruffleSplittingEnabled.getValue()) {
-                return false;
-            }
-            if (!isSplittable()) {
-                return false;
-            }
-            int nodeCount = NodeUtil.countNodes(getCallTarget().getRootNode(), OptimizedCallNodeProfile.COUNT_FILTER, false);
-            if (nodeCount > TruffleCompilerOptions.TruffleSplittingMaxCalleeSize.getValue()) {
-                return false;
-            }
+    /**
+     * If the method was inlined the truffle magic redirects calls to
+     * {@link #call(PackedFrame, Arguments)} to this method. You should not call this method
+     * directly.
+     *
+     * @see PartialEvaluator#expandInlinableCallNode
+     */
+    public Object callInlined(PackedFrame caller, Arguments arguments) {
+        return getCurrentCallTarget().callInlined(caller, arguments);
+    }
 
-            // disable recursive splitting for now
-            OptimizedCallTarget splitTarget = getCallTarget();
-            List<OptimizedCallTarget> compilationRoots = OptimizedCallNodeProfile.findCompilationRoots(this);
-            for (OptimizedCallTarget compilationRoot : compilationRoots) {
-                if (compilationRoot == splitTarget || compilationRoot.getSplitSource() == splitTarget) {
-                    // recursive call found
-                    return false;
-                }
-            }
+    @Override
+    public void inline() {
+        inliningForced = true;
+    }
 
-            // max one child call and callCount > 2 and kind of small number of nodes
-            if (isMaxSingleCall()) {
-                return true;
-            }
-            return countPolymorphic() >= 1;
-        }
+    @Override
+    public boolean isInlined() {
+        return inliningForced;
+    }
 
-        @Override
-        public void nodeReplaced(Node oldNode, Node newNode, CharSequence reason) {
-            trySplit = true;
-        }
+    private void splitImpl(boolean heuristic) {
+        CompilerAsserts.neverPartOfCompilation();
 
-        private boolean isMaxSingleCall() {
-            return NodeUtil.countNodes(getCurrentCallTarget().getRootNode(), new NodeCountFilter() {
-                public boolean isCounted(Node node) {
-                    return node instanceof CallNode;
-                }
-            }) <= 1;
+        OptimizedCallTarget splitTarget = (OptimizedCallTarget) Truffle.getRuntime().createCallTarget(getCallTarget().getRootNode().split());
+        splitTarget.setSplitSource(getCallTarget());
+        if (heuristic) {
+            OptimizedCallTarget.logSplit(this, getCallTarget(), splitTarget);
         }
+        if (callCount >= 1) {
+            getCallTarget().decrementKnownCallSite();
+            splitTarget.incrementKnownCallSite();
+        }
+        this.splitCallTarget = splitTarget;
+    }
 
-        private int countPolymorphic() {
-            return NodeUtil.countNodes(getCallTarget().getRootNode(), new NodeCountFilter() {
-                public boolean isCounted(Node node) {
-                    NodeCost cost = node.getCost();
-                    boolean polymorphic = cost == NodeCost.POLYMORPHIC || cost == NodeCost.MEGAMORPHIC;
-                    return polymorphic;
-                }
-            });
+    private boolean shouldSplit() {
+        if (splitCallTarget != null) {
+            return false;
+        }
+        if (!TruffleCompilerOptions.TruffleSplittingEnabled.getValue()) {
+            return false;
         }
-
-        @Override
-        public boolean isInlined() {
+        if (!isSplittable()) {
+            return false;
+        }
+        OptimizedCallTarget splitTarget = getCallTarget();
+        int nodeCount = OptimizedCallUtils.countNonTrivialNodes(null, new TruffleCallPath(splitTarget));
+        if (nodeCount > TruffleCompilerOptions.TruffleSplittingMaxCalleeSize.getValue()) {
             return false;
         }
 
-        @Override
-        public boolean split() {
-            if (!isSplittable()) {
-                // split is only allowed once and if the root node supports it
-                return false;
-            }
-            if (getParent() == null) {
-                throw new IllegalStateException("CallNode must be adopted before it is split.");
-            }
-            splitImpl(false);
-            return true;
-        }
-
-        private OptimizedCallNode splitImpl(boolean heuristic) {
-            OptimizedCallTarget splitCallTarget = (OptimizedCallTarget) Truffle.getRuntime().createCallTarget(getCallTarget().getRootNode().split());
-            splitCallTarget.setSplitSource(getCallTarget());
-            if (heuristic) {
-                OptimizedCallTarget.logSplit(this, getCallTarget(), splitCallTarget);
-            }
-            return replace(new SplitOptimizedCallNode(getCallTarget(), splitCallTarget, callCount));
-        }
-
-        @Override
-        public void inline() {
-            inlineImpl();
-        }
-
-        @Override
-        public OptimizedCallTarget getSplitCallTarget() {
-            return null;
-        }
-
-    }
-
-    private static final class InlinedOptimizedCallNode extends OptimizedCallNode {
-
-        private final OptimizedCallTarget splittedTarget;
-
-        public InlinedOptimizedCallNode(OptimizedCallTarget target, OptimizedCallTarget splittedTarget, int callCount) {
-            super(target);
-            this.splittedTarget = splittedTarget;
-            this.callCount = callCount;
-        }
-
-        @Override
-        public Object call(PackedFrame caller, Arguments arguments) {
-            if (CompilerDirectives.inInterpreter()) {
-                callCount++;
-            }
-            return getCurrentCallTarget().callInlined(caller, arguments);
-        }
-
-        @Override
-        public void inline() {
-        }
-
-        @Override
-        public boolean split() {
+        // disable recursive splitting for now
+        OptimizedCallTarget root = (OptimizedCallTarget) getRootNode().getCallTarget();
+        if (root == splitTarget || root.getSplitSource() == splitTarget) {
+            // recursive call found
             return false;
         }
 
-        @Override
-        public boolean isInlined() {
+        // max one child call and callCount > 2 and kind of small number of nodes
+        if (isMaxSingleCall()) {
             return true;
         }
+        return countPolymorphic() >= 1;
+    }
 
-        @Override
-        public OptimizedCallTarget getSplitCallTarget() {
-            return splittedTarget;
-        }
+    private boolean isMaxSingleCall() {
+        return NodeUtil.countNodes(getCurrentCallTarget().getRootNode(), new NodeCountFilter() {
+            public boolean isCounted(Node node) {
+                return node instanceof CallNode;
+            }
+        }) <= 1;
     }
 
-    private static class SplitOptimizedCallNode extends OptimizedCallNode {
-
-        private final OptimizedCallTarget splittedTarget;
-
-        public SplitOptimizedCallNode(OptimizedCallTarget target, OptimizedCallTarget splittedTarget, int callCount) {
-            super(target);
-            this.callCount = callCount;
-            this.splittedTarget = splittedTarget;
-        }
-
-        @Override
-        public Object call(PackedFrame caller, Arguments arguments) {
-            if (CompilerDirectives.inInterpreter()) {
-                callCount++;
+    private int countPolymorphic() {
+        return NodeUtil.countNodes(getCallTarget().getRootNode(), new NodeCountFilter() {
+            public boolean isCounted(Node node) {
+                NodeCost cost = node.getCost();
+                boolean polymorphic = cost == NodeCost.POLYMORPHIC || cost == NodeCost.MEGAMORPHIC;
+                return polymorphic;
             }
-            return splittedTarget.call(caller, arguments);
-        }
-
-        @Override
-        public boolean isInlined() {
-            return false;
-        }
-
-        @Override
-        public final boolean split() {
-            return false;
-        }
-
-        @Override
-        public void inline() {
-            inlineImpl();
-        }
-
-        @Override
-        public final OptimizedCallTarget getSplitCallTarget() {
-            return splittedTarget;
-        }
-
+        });
     }
 
+    @SuppressWarnings("unused")
+    public void nodeReplaced(Node oldNode, Node newNode, CharSequence reason) {
+        if (!isSplit() && isSplittable()) {
+            trySplit = true;
+        }
+    }
 }
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallNodeProfile.java	Thu Apr 03 18:33:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-/*
- * Copyright (c) 2013, 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.
- *
- * 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 com.oracle.graal.truffle;
-
-import static com.oracle.graal.truffle.TruffleCompilerOptions.*;
-
-import java.util.*;
-
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.nodes.NodeUtil.*;
-
-public class OptimizedCallNodeProfile implements TruffleInliningProfile {
-
-    static final CountFilter COUNT_FILTER = new CountFilter();
-
-    private static final String REASON_RECURSION = "recursion";
-    private static final String REASON_MAXIMUM_NODE_COUNT = "shallowTargetCount  > " + TruffleInliningMaxCalleeSize.getValue();
-    private static final String REASON_MAXIMUM_TOTAL_NODE_COUNT = "inlinedTotalCount > " + TruffleInliningMaxCallerSize.getValue();
-
-    private final OptimizedCallTarget callTarget;
-    private final OptimizedCallNode callNode;
-
-    private int targetDeepNodeCount;
-    private List<OptimizedCallTarget> compilationRoots;
-    private final int targetShallowNodeCount;
-    private final double averageFrequency;
-    private final double score;
-    private final boolean leaf;
-    private String reason;
-
-    public OptimizedCallNodeProfile(OptimizedCallTarget target, OptimizedCallNode callNode) {
-        this.callNode = callNode;
-        RootNode inlineRoot = callNode.getCurrentCallTarget().getRootNode();
-        this.callTarget = target;
-        this.targetShallowNodeCount = NodeUtil.countNodes(inlineRoot, COUNT_FILTER, false);
-        this.targetDeepNodeCount = NodeUtil.countNodes(inlineRoot, COUNT_FILTER, true);
-        this.compilationRoots = findCompilationRoots(callNode);
-        this.averageFrequency = calculateFrequency();
-        this.score = calculateScore();
-        this.leaf = calculateLeaf();
-
-    }
-
-    private boolean calculateLeaf() {
-        return NodeUtil.countNodes(callNode.getCurrentRootNode(), new NodeCountFilter() {
-            public boolean isCounted(Node node) {
-                if (node instanceof CallNode) {
-                    CallNode childCall = (CallNode) node;
-                    if (!childCall.isInlined()) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        }, true) <= 0;
-    }
-
-    private double calculateFrequency() {
-        return calculateAdvancedFrequency();
-    }
-
-    public OptimizedCallNode getCallNode() {
-        return callNode;
-    }
-
-    public double getScore() {
-        return score;
-    }
-
-    public double calculateScore() {
-        return averageFrequency / Math.max(1, targetDeepNodeCount);
-    }
-
-    public boolean isInliningAllowed() {
-        this.compilationRoots = findCompilationRoots(getCallNode());
-
-        OptimizedCallTarget inlineTarget = callNode.getCurrentCallTarget();
-        for (OptimizedCallTarget compilationRoot : compilationRoots) {
-            if (compilationRoot == inlineTarget) {
-                // recursive call found
-                reason = REASON_RECURSION;
-                return false;
-            }
-        }
-
-        if (targetShallowNodeCount > TruffleInliningMaxCalleeSize.getValue()) {
-            reason = REASON_MAXIMUM_NODE_COUNT;
-            return false;
-        }
-
-        this.targetDeepNodeCount = NodeUtil.countNodes(inlineTarget.getRootNode(), COUNT_FILTER, true);
-        // The maximum total node count cannot be cached since it may change during inlining.
-        int nextNodeCount = calculateInlinedTotalNodeCount(getCallNode());
-        if (nextNodeCount > TruffleInliningMaxCallerSize.getValue()) {
-            reason = REASON_MAXIMUM_TOTAL_NODE_COUNT;
-            return false;
-        }
-
-        return true;
-    }
-
-    private int calculateInlinedTotalNodeCount(OptimizedCallNode node) {
-        int currentNodeCount = 0;
-        for (OptimizedCallTarget compilationRoot : compilationRoots) {
-            TotalNodeCountVisitor visitor = new TotalNodeCountVisitor(node, targetDeepNodeCount);
-            compilationRoot.getRootNode().accept(visitor);
-            currentNodeCount = Math.max(currentNodeCount, visitor.count);
-        }
-        return currentNodeCount;
-    }
-
-    static class CountFilter implements NodeCountFilter {
-        public boolean isCounted(Node node) {
-            return countNode(node) >= 1;
-        }
-    }
-
-    static int countNode(Node node) {
-        NodeCost cost = node.getCost();
-        if (cost != null && cost != NodeCost.NONE && cost != NodeCost.UNINITIALIZED) {
-            return 1;
-        }
-        return 0;
-    }
-
-    private static class TotalNodeCountVisitor implements NodeVisitor {
-
-        private final OptimizedCallNode inlinedNode;
-        private final int inlinedNodeCount;
-
-        private int count;
-
-        public TotalNodeCountVisitor(OptimizedCallNode inlinedNode, int inlinedNodeCount) {
-            this.inlinedNode = inlinedNode;
-            this.inlinedNodeCount = inlinedNodeCount;
-        }
-
-        public boolean visit(Node node) {
-            count += countNode(node);
-            if (node instanceof OptimizedCallNode) {
-                OptimizedCallNode callNode = ((OptimizedCallNode) node);
-                if (callNode == inlinedNode) {
-                    count += inlinedNodeCount;
-                } else if (callNode.isInlined()) {
-                    callNode.getCurrentRootNode().accept(this);
-                }
-            }
-            return true;
-        }
-
-    }
-
-    double calculateAdvancedFrequency() {
-        // get the call hierarchy from call target to the call node
-        final ArrayDeque<OptimizedCallNode> callStack = new ArrayDeque<>();
-        callTarget.getRootNode().accept(new NodeVisitor() {
-            private boolean found = false;
-
-            public boolean visit(Node node) {
-                if (node == callNode) {
-                    // found our call
-                    callStack.push((OptimizedCallNode) node);
-                    found = true;
-                    return false;
-                }
-
-                if (node instanceof OptimizedCallNode) {
-                    OptimizedCallNode c = ((OptimizedCallNode) node);
-                    if (c.isInlined()) {
-                        if (!found) {
-                            callStack.push(c);
-                        }
-                        c.getCurrentRootNode().accept(this);
-                        if (!found) {
-                            callStack.pop();
-                        }
-                    }
-                }
-                return !found;
-            }
-        });
-
-        int parentCallCount = callTarget.getCompilationProfile().getCallCount();
-        double frequency = 1.0d;
-        for (OptimizedCallNode c : callStack) {
-            int childCallCount = c.getCallCount();
-            frequency *= childCallCount / (double) parentCallCount;
-            parentCallCount = c.getCurrentCallTarget().getCompilationProfile().getCallCount();
-        }
-        return frequency;
-    }
-
-    double calculateSimpleFrequency() {
-        return callNode.getCallCount() / (double) ((OptimizedCallTarget) callNode.getRootNode().getCallTarget()).getCompilationProfile().getCallCount();
-    }
-
-    static List<OptimizedCallTarget> findCompilationRoots(Node call) {
-        RootNode root = call.getRootNode();
-        if (root == null) {
-            return Collections.emptyList();
-        }
-        List<OptimizedCallTarget> roots = new ArrayList<>();
-        roots.add((OptimizedCallTarget) root.getCallTarget());
-        for (CallNode callNode : root.getCachedCallNodes()) {
-            if (callNode.isInlined()) {
-                roots.addAll(findCompilationRoots(callNode));
-            }
-        }
-        return roots;
-    }
-
-    public int compareTo(TruffleInliningProfile o) {
-        if (o instanceof OptimizedCallNodeProfile) {
-            int cmp = Boolean.compare(((OptimizedCallNodeProfile) o).leaf, leaf);
-            if (cmp == 0) {
-                return Double.compare(((OptimizedCallNodeProfile) o).getScore(), getScore());
-            }
-            return cmp;
-        }
-        return 0;
-    }
-
-    public Map<String, Object> getDebugProperties() {
-        Map<String, Object> properties = new LinkedHashMap<>();
-        OptimizedCallTarget.addASTSizeProperty(getCallNode().getCurrentRootNode().getRootNode(), properties);
-        properties.put("shallowCount", targetShallowNodeCount);
-        properties.put("currentCount", calculateInlinedTotalNodeCount(null));
-        properties.put("inlinedTotalCount", calculateInlinedTotalNodeCount(getCallNode()));
-        properties.put("score", score);
-        properties.put("frequency", averageFrequency);
-        properties.put("leaf", leaf);
-        properties.put("reason", reason);
-        return properties;
-    }
-
-}
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallTarget.java	Thu Apr 03 18:33:48 2014 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallTarget.java	Thu Apr 03 19:06:58 2014 +0200
@@ -26,14 +26,15 @@
 
 import java.io.*;
 import java.util.*;
+import java.util.concurrent.atomic.*;
 
 import com.oracle.graal.api.code.*;
 import com.oracle.graal.debug.*;
+import com.oracle.graal.truffle.OptimizedCallUtils.InlinedNodeCountFilter;
 import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.api.impl.*;
 import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.nodes.NodeUtil.NodeCountFilter;
 
 /**
  * Call target that is optimized by Graal upon surpassing a specific invocation threshold.
@@ -44,26 +45,41 @@
 
     protected InstalledCode installedCode;
     protected boolean compilationEnabled;
-    private boolean inlined;
     protected int callCount;
-
+    private TruffleInliningResult inliningResult;
     protected final CompilationProfile compilationProfile;
     protected final CompilationPolicy compilationPolicy;
-    private SpeculationLog speculationLog = new SpeculationLog();
+    private final SpeculationLog speculationLog = new SpeculationLog();
     private OptimizedCallTarget splitSource;
 
+    private final AtomicInteger callSitesKnown = new AtomicInteger(0);
+
     public OptimizedCallTarget(RootNode rootNode, int invokeCounter, int compilationThreshold, boolean compilationEnabled, CompilationPolicy compilationPolicy) {
         super(rootNode);
-
         this.compilationEnabled = compilationEnabled;
         this.compilationPolicy = compilationPolicy;
-
         this.compilationProfile = new CompilationProfile(compilationThreshold, invokeCounter, rootNode.toString());
         if (TruffleCallTargetProfiling.getValue()) {
             registerCallTarget(this);
         }
     }
 
+    public int getKnownCallSiteCount() {
+        return callSitesKnown.get();
+    }
+
+    public void incrementKnownCallSite() {
+        callSitesKnown.incrementAndGet();
+    }
+
+    public void decrementKnownCallSite() {
+        callSitesKnown.decrementAndGet();
+    }
+
+    public TruffleInliningResult getInliningResult() {
+        return inliningResult;
+    }
+
     public OptimizedCallTarget getSplitSource() {
         return splitSource;
     }
@@ -100,56 +116,19 @@
         return executeHelper(caller, arguments);
     }
 
-    public void performInlining() {
-        if (!TruffleCompilerOptions.TruffleFunctionInlining.getValue()) {
-            return;
-        }
-        if (inlined) {
+    public final void performInlining() {
+        if (!shouldInline()) {
             return;
         }
-        inlined = true;
 
-        logInliningStart(this);
-        PriorityQueue<TruffleInliningProfile> queue = new PriorityQueue<>();
-
-        // Used to avoid running in cycles or inline nodes in Truffle trees
-        // which do not suffice the tree property.
-        Set<CallNode> visitedCallNodes = new HashSet<>();
+        if (inliningResult != null) {
+            return;
+        }
 
-        queueCallSitesForInlining(this, getRootNode(), visitedCallNodes, queue);
-        TruffleInliningProfile callSite = queue.poll();
-        while (callSite != null) {
-            if (callSite.isInliningAllowed()) {
-                OptimizedCallNode callNode = callSite.getCallNode();
-                RootNode inlinedRoot = callNode.inlineImpl().getCurrentRootNode();
-                logInlined(this, callSite);
-                assert inlinedRoot != null;
-                queueCallSitesForInlining(this, inlinedRoot, visitedCallNodes, queue);
-            } else {
-                logInliningFailed(callSite);
-            }
-            callSite = queue.poll();
-        }
-        logInliningDone(this);
-    }
-
-    private static void queueCallSitesForInlining(final OptimizedCallTarget target, RootNode rootNode, final Set<CallNode> visitedCallSites, final PriorityQueue<TruffleInliningProfile> queue) {
-        rootNode.accept(new NodeVisitor() {
-            public boolean visit(Node node) {
-                if (node instanceof OptimizedCallNode) {
-                    OptimizedCallNode call = ((OptimizedCallNode) node);
-                    if (!call.isInlined() && !visitedCallSites.contains(call)) {
-                        queue.add(call.createInliningProfile(target));
-                        visitedCallSites.add(call);
-                    }
-                    RootNode root = call.getCurrentRootNode();
-                    if (root != null && call.isInlined()) {
-                        root.accept(this);
-                    }
-                }
-                return true;
-            }
-        });
+        TruffleInliningHandler handler = new TruffleInliningHandler(this, new DefaultInliningPolicy(), new HashMap<OptimizedCallTarget, TruffleInliningResult>());
+        int startNodeCount = OptimizedCallUtils.countNonTrivialNodes(null, new TruffleCallPath(this));
+        this.inliningResult = handler.inline(startNodeCount);
+        logInliningDecision(this, inliningResult, handler);
     }
 
     protected boolean shouldCompile() {
@@ -160,6 +139,26 @@
         return TruffleFunctionInlining.getValue();
     }
 
+    protected final void cancelInlinedCallOptimization() {
+        if (getInliningResult() != null) {
+            for (TruffleCallPath path : getInliningResult()) {
+                OptimizedCallNode top = path.getCallNode();
+                top.notifyInlining();
+                top.getCurrentCallTarget().cancelInstalledTask(top, top, "Inlined");
+            }
+        }
+    }
+
+    protected final void onCompilationDone() {
+        if (inliningResult != null) {
+            for (TruffleCallPath path : inliningResult) {
+                path.getCallNode().notifyInliningDone();
+            }
+        }
+    }
+
+    protected abstract void cancelInstalledTask(Node oldNode, Node newNode, CharSequence reason);
+
     protected abstract void invalidate(Node oldNode, Node newNode, CharSequence reason);
 
     public Object executeHelper(PackedFrame caller, Arguments args) {
@@ -174,32 +173,12 @@
     @Override
     public void reportLoopCount(int count) {
         compilationProfile.reportLoopCount(count);
-
-        // delegate to inlined call sites
-        for (CallNode callNode : getRootNode().getCachedCallNodes()) {
-            if (callNode.isInlined()) {
-                callNode.getRootNode().reportLoopCount(count);
-            }
-        }
     }
 
     @Override
     public void nodeReplaced(Node oldNode, Node newNode, CharSequence reason) {
         compilationProfile.reportNodeReplaced();
         invalidate(oldNode, newNode, reason);
-
-        // delegate to inlined call sites
-        for (CallNode callNode : getRootNode().getCachedCallNodes()) {
-            if (callNode.isInlined()) {
-                CallTarget target = callNode.getRootNode().getCallTarget();
-                if (target instanceof ReplaceObserver) {
-                    ((ReplaceObserver) target).nodeReplaced(oldNode, newNode, reason);
-                }
-            }
-            if (callNode instanceof OptimizedCallNode) {
-                ((OptimizedCallNode) callNode).nodeReplaced(oldNode, newNode, reason);
-            }
-        }
     }
 
     public SpeculationLog getSpeculationLog() {
@@ -208,66 +187,49 @@
 
     public Map<String, Object> getDebugProperties() {
         Map<String, Object> properties = new LinkedHashMap<>();
-        addASTSizeProperty(getRootNode(), properties);
+        addASTSizeProperty(getInliningResult(), new TruffleCallPath(this), properties);
         properties.putAll(getCompilationProfile().getDebugProperties());
         return properties;
 
     }
 
-    private static void logInliningFailed(TruffleInliningProfile callSite) {
-        if (TraceTruffleInliningDetails.getValue()) {
-            log(2, "inline failed", callSite.getCallNode().getCurrentCallTarget().toString(), callSite.getDebugProperties());
+    private static void logInliningDecision(OptimizedCallTarget target, TruffleInliningResult result, TruffleInliningHandler handler) {
+        if (!TraceTruffleInlining.getValue()) {
+            return;
         }
+
+        List<TruffleInliningProfile> profiles = handler.lookupProfiles(result, new TruffleCallPath(target));
+
+        Collections.sort(profiles); // sorts by hierarchy and source section
+
+        logInliningStart(target);
+        for (TruffleInliningProfile profile : profiles) {
+            TruffleCallPath path = profile.getCallPath();
+            if (path.getRootCallTarget() == target) {
+                String msg = result.isInlined(path) ? "inline success" : "inline failed";
+                logInlinedImpl(msg, result, handler.getProfiles().get(path), path);
+            }
+        }
+        logInliningDone(target);
     }
 
-    private static void logInlined(final OptimizedCallTarget target, TruffleInliningProfile callSite) {
-        if (TraceTruffleInliningDetails.getValue() || TraceTruffleInlining.getValue()) {
-            log(2, "inline success", callSite.getCallNode().getCurrentCallTarget().toString(), callSite.getDebugProperties());
-
-            if (TraceTruffleInliningDetails.getValue()) {
-                RootNode root = callSite.getCallNode().getCurrentCallTarget().getRootNode();
-                root.accept(new NodeVisitor() {
-                    int depth = 1;
-
-                    public boolean visit(Node node) {
-                        if (node instanceof OptimizedCallNode) {
-                            OptimizedCallNode callNode = ((OptimizedCallNode) node);
-                            RootNode inlinedRoot = callNode.getCurrentRootNode();
-
-                            if (inlinedRoot != null) {
-                                Map<String, Object> properties = new LinkedHashMap<>();
-                                addASTSizeProperty(callNode.getCurrentRootNode(), properties);
-                                properties.putAll(callNode.createInliningProfile(target).getDebugProperties());
-                                String message;
-                                if (callNode.isInlined()) {
-                                    message = "inline success";
-                                } else {
-                                    message = "inline dispatch";
-                                }
-                                log(2 + (depth * 2), message, callNode.getCurrentCallTarget().toString(), properties);
-
-                                if (callNode.isInlined()) {
-                                    depth++;
-                                    inlinedRoot.accept(this);
-                                    depth--;
-                                }
-                            }
-                        }
-                        return true;
-                    }
-                });
-            }
+    private static void logInlinedImpl(String status, TruffleInliningResult result, TruffleInliningProfile profile, TruffleCallPath path) {
+        Map<String, Object> properties = new LinkedHashMap<>();
+        addASTSizeProperty(result, path, properties);
+        if (profile != null) {
+            properties.putAll(profile.getDebugProperties());
         }
+        log((path.getDepth() * 2), status, path.getCallTarget().toString(), properties);
     }
 
     private static void logInliningStart(OptimizedCallTarget target) {
-        if (TraceTruffleInliningDetails.getValue()) {
+        if (TraceTruffleInlining.getValue()) {
             log(0, "inline start", target.toString(), target.getDebugProperties());
         }
     }
 
     private static void logInliningDone(OptimizedCallTarget target) {
-        if (TraceTruffleInliningDetails.getValue()) {
+        if (TraceTruffleInlining.getValue()) {
             log(0, "inline done", target.toString(), target.getDebugProperties());
         }
     }
@@ -349,29 +311,27 @@
     static void logSplit(OptimizedCallNode callNode, OptimizedCallTarget target, OptimizedCallTarget newTarget) {
         if (TraceTruffleSplitting.getValue()) {
             Map<String, Object> properties = new LinkedHashMap<>();
-            addASTSizeProperty(target.getRootNode(), properties);
+            addASTSizeProperty(target.getInliningResult(), new TruffleCallPath(target), properties);
             properties.put("Split#", ++splitCount);
             properties.put("Source", callNode.getEncapsulatingSourceSection());
             log(0, "split", newTarget.toString(), properties);
         }
     }
 
-    static void addASTSizeProperty(RootNode target, Map<String, Object> properties) {
-        int polymorphicCount = NodeUtil.countNodes(target.getRootNode(), new NodeCountFilter() {
-            public boolean isCounted(Node node) {
+    static void addASTSizeProperty(TruffleInliningResult inliningResult, TruffleCallPath countedPath, Map<String, Object> properties) {
+        int polymorphicCount = OptimizedCallUtils.countNodes(inliningResult, countedPath, new InlinedNodeCountFilter() {
+            public boolean isCounted(TruffleCallPath path, Node node) {
                 return node.getCost() == NodeCost.POLYMORPHIC;
             }
-        }, true);
+        });
 
-        int megamorphicCount = NodeUtil.countNodes(target.getRootNode(), new NodeCountFilter() {
-            public boolean isCounted(Node node) {
+        int megamorphicCount = OptimizedCallUtils.countNodes(inliningResult, countedPath, new InlinedNodeCountFilter() {
+            public boolean isCounted(TruffleCallPath path, Node node) {
                 return node.getCost() == NodeCost.MEGAMORPHIC;
             }
-        }, true);
+        });
 
-        String value = String.format("%4d (%d/%d)", NodeUtil.countNodes(target.getRootNode(), OptimizedCallNodeProfile.COUNT_FILTER, true), //
-                        polymorphicCount, megamorphicCount); //
-
+        String value = String.format("%4d (%d/%d)", OptimizedCallUtils.countNonTrivialNodes(inliningResult, countedPath), polymorphicCount, megamorphicCount);
         properties.put("ASTSize", value);
     }
 
@@ -429,15 +389,13 @@
                 continue;
             }
 
-            int nodeCount = NodeUtil.countNodes(callTarget.getRootNode(), OptimizedCallNodeProfile.COUNT_FILTER, false);
-            int inlinedCallSiteCount = NodeUtil.countNodes(callTarget.getRootNode(), OptimizedCallNodeProfile.COUNT_FILTER, true);
+            int nodeCount = OptimizedCallUtils.countNonTrivialNodes(callTarget.getInliningResult(), new TruffleCallPath(callTarget));
             String comment = callTarget.installedCode == null ? " int" : "";
             comment += callTarget.compilationEnabled ? "" : " fail";
-            OUT.printf("%-50s | %10d | %15d | %10d | %3d%s\n", callTarget.getRootNode(), callTarget.callCount, inlinedCallSiteCount, nodeCount,
-                            callTarget.getCompilationProfile().getInvalidationCount(), comment);
+            OUT.printf("%-50s | %10d | %15d | %10d | %3d%s\n", callTarget.getRootNode(), callTarget.callCount, nodeCount, nodeCount, callTarget.getCompilationProfile().getInvalidationCount(), comment);
 
             totalCallCount += callTarget.callCount;
-            totalInlinedCallSiteCount += inlinedCallSiteCount;
+            totalInlinedCallSiteCount += nodeCount;
             totalNodeCount += nodeCount;
             totalInvalidationCount += callTarget.getCompilationProfile().getInvalidationCount();
         }
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallTargetImpl.java	Thu Apr 03 18:33:48 2014 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallTargetImpl.java	Thu Apr 03 19:06:58 2014 +0200
@@ -96,7 +96,8 @@
         cancelInstalledTask(oldNode, newNode, reason);
     }
 
-    private void cancelInstalledTask(Node oldNode, Node newNode, CharSequence reason) {
+    @Override
+    protected void cancelInstalledTask(Node oldNode, Node newNode, CharSequence reason) {
         Future<InstalledCode> task = this.installedCodeTask;
         if (task != null) {
             task.cancel(true);
@@ -145,6 +146,7 @@
             return null;
         } else {
             performInlining();
+            cancelInlinedCallOptimization();
             logOptimizingQueued(this);
             this.installedCodeTask = compiler.compile(this);
             if (!TruffleBackgroundCompilation.getValue()) {
@@ -171,6 +173,8 @@
                 }
             }
             return null;
+        } finally {
+            onCompilationDone();
         }
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/OptimizedCallUtils.java	Thu Apr 03 19:06:58 2014 +0200
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ * 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 com.oracle.graal.truffle;
+
+import com.oracle.truffle.api.nodes.*;
+
+class OptimizedCallUtils {
+
+    public abstract static class InlinedCallVisitor implements NodeVisitor {
+
+        private TruffleCallPath currentPath;
+        private final TruffleInliningResult inliningDecision;
+
+        public InlinedCallVisitor(TruffleInliningResult inliningDecision, TruffleCallPath initialPath) {
+            this.inliningDecision = inliningDecision;
+            this.currentPath = initialPath;
+        }
+
+        public final TruffleInliningResult getInliningDecision() {
+            return inliningDecision;
+        }
+
+        public final boolean visit(Node node) {
+            if (node instanceof OptimizedCallNode) {
+                OptimizedCallNode callNode = ((OptimizedCallNode) node);
+                this.currentPath = new TruffleCallPath(this.currentPath, callNode);
+                try {
+                    boolean result = visit(currentPath, node);
+                    TruffleInliningResult decision = inliningDecision;
+                    if (decision != null && decision.isInlined(currentPath)) {
+                        callNode.getCurrentRootNode().accept(this);
+                    }
+                    return result;
+                } finally {
+                    this.currentPath = this.currentPath.getParent();
+                }
+            } else {
+                return visit(currentPath, node);
+            }
+        }
+
+        public abstract boolean visit(TruffleCallPath path, Node node);
+
+    }
+
+    public static int countNodes(TruffleInliningResult decision, TruffleCallPath path, InlinedNodeCountFilter filter) {
+        InlinedNodeCountVisitor nodeCount = new InlinedNodeCountVisitor(decision, path, filter);
+        path.getCallTarget().getRootNode().accept(nodeCount);
+        return nodeCount.nodeCount;
+    }
+
+    public static int countCalls(TruffleInliningResult decision, TruffleCallPath path) {
+        InlinedNodeCountVisitor nodeCount = new InlinedNodeCountVisitor(decision, path, new InlinedNodeCountFilter() {
+            public boolean isCounted(TruffleCallPath p, Node node) {
+                return node instanceof CallNode;
+            }
+        });
+        path.getCallTarget().getRootNode().accept(nodeCount);
+        return nodeCount.nodeCount;
+    }
+
+    public interface InlinedNodeCountFilter {
+
+        boolean isCounted(TruffleCallPath path, Node node);
+    }
+
+    private static final class InlinedNodeCountVisitor extends InlinedCallVisitor {
+
+        private final InlinedNodeCountFilter filter;
+        int nodeCount;
+
+        private InlinedNodeCountVisitor(TruffleInliningResult decision, TruffleCallPath initialPath, InlinedNodeCountFilter filter) {
+            super(decision, initialPath);
+            this.filter = filter;
+        }
+
+        @Override
+        public boolean visit(TruffleCallPath path, Node node) {
+            if (filter == null || filter.isCounted(path, node)) {
+                nodeCount++;
+            }
+            return true;
+        }
+
+    }
+
+    static int countNonTrivialNodes(TruffleInliningResult state, TruffleCallPath path) {
+        return countNodes(state, path, new InlinedNodeCountFilter() {
+
+            public boolean isCounted(TruffleCallPath p, Node node) {
+                NodeCost cost = node.getCost();
+                if (cost != null && cost != NodeCost.NONE && cost != NodeCost.UNINITIALIZED) {
+                    return true;
+                }
+                return false;
+            }
+        });
+    }
+
+}
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/PartialEvaluator.java	Thu Apr 03 18:33:48 2014 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/PartialEvaluator.java	Thu Apr 03 19:06:58 2014 +0200
@@ -56,6 +56,7 @@
 import com.oracle.graal.truffle.phases.*;
 import com.oracle.graal.virtual.phases.ea.*;
 import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.api.nodes.*;
 
 /**
@@ -68,6 +69,7 @@
     private Set<Constant> constantReceivers;
     private final TruffleCache truffleCache;
     private final ResolvedJavaType frameType;
+    private final ResolvedJavaMethod callInlinedMethod;
 
     public PartialEvaluator(Providers providers, TruffleCache truffleCache) {
         this.providers = providers;
@@ -75,6 +77,11 @@
         this.canonicalizer = new CanonicalizerPhase(!ImmutableCode.getValue(), customCanonicalizer);
         this.truffleCache = truffleCache;
         this.frameType = providers.getMetaAccess().lookupJavaType(FrameWithoutBoxing.class);
+        try {
+            callInlinedMethod = providers.getMetaAccess().lookupJavaMethod(OptimizedCallNode.class.getDeclaredMethod("callInlined", PackedFrame.class, Arguments.class));
+        } catch (NoSuchMethodException ex) {
+            throw new RuntimeException(ex);
+        }
     }
 
     public StructuredGraph createGraph(final OptimizedCallTarget callTarget, final Assumptions assumptions) {
@@ -112,10 +119,10 @@
             Debug.dump(graph, "Before inlining");
 
             // Make sure frame does not escape.
-            expandTree(graph, assumptions);
+            expandTree(callTarget, graph, assumptions);
 
-            if (Thread.interrupted()) {
-                return graph;
+            if (Thread.currentThread().isInterrupted()) {
+                return null;
             }
 
             new VerifyFrameDoesNotEscapePhase().apply(graph, false);
@@ -166,12 +173,13 @@
         return graph;
     }
 
-    private void expandTree(StructuredGraph graph, Assumptions assumptions) {
+    private void expandTree(OptimizedCallTarget target, StructuredGraph graph, Assumptions assumptions) {
         PhaseContext phaseContext = new PhaseContext(providers, assumptions);
         TruffleExpansionLogger expansionLogger = null;
         if (TraceTruffleExpansion.getValue()) {
             expansionLogger = new TruffleExpansionLogger(graph);
         }
+        Map<Node, TruffleCallPath> methodTargetToStack = new HashMap<>();
         boolean changed;
         do {
             changed = false;
@@ -197,13 +205,18 @@
                         }
 
                         StructuredGraph inlineGraph = replacements.getMethodSubstitution(methodCallTargetNode.targetMethod());
+
+                        if (inlineGraph == null) {
+                            inlineGraph = expandInlinableCallNode(target, methodTargetToStack, assumptions, phaseContext, methodCallTargetNode);
+                        }
+
                         if (inlineGraph == null && !Modifier.isNative(methodCallTargetNode.targetMethod().getModifiers())) {
                             inlineGraph = parseGraph(methodCallTargetNode.targetMethod(), methodCallTargetNode.arguments(), assumptions, phaseContext);
                         }
 
                         if (inlineGraph != null) {
                             try (Indent indent = Debug.logAndIndent("inline graph %s", methodCallTargetNode.targetMethod())) {
-
+                                preExpandTruffleCallPath(inlineGraph, methodTargetToStack, methodTargetToStack.get(methodCallTargetNode));
                                 int nodeCountBefore = graph.getNodeCount();
                                 Mark mark = graph.getMark();
                                 if (TraceTruffleExpansion.getValue()) {
@@ -215,6 +228,7 @@
                                 if (TraceTruffleExpansion.getValue()) {
                                     expansionLogger.postExpand(inlined);
                                 }
+                                postExpandTruffleCallPath(methodTargetToStack, inlined);
                                 // }
                                 if (Debug.isDumpEnabled()) {
                                     Debug.log("dump enabled");
@@ -228,10 +242,6 @@
                     }
                 }
 
-                if (Thread.interrupted()) {
-                    return;
-                }
-
                 if (graph.getNodeCount() > TruffleCompilerOptions.TruffleGraphMaxNodes.getValue()) {
                     throw new BailoutException("Truffle compilation is exceeding maximum node count: " + graph.getNodeCount());
                 }
@@ -243,6 +253,59 @@
         }
     }
 
+    private static void preExpandTruffleCallPath(StructuredGraph inlineGraph, Map<Node, TruffleCallPath> methodTargetToCallPath, TruffleCallPath truffleCallPath) {
+        for (MethodCallTargetNode methodTargetNode : inlineGraph.getNodes(MethodCallTargetNode.class)) {
+            methodTargetToCallPath.put(methodTargetNode, truffleCallPath);
+        }
+    }
+
+    private static void postExpandTruffleCallPath(Map<Node, TruffleCallPath> methodCallTargetToCallPath, Map<Node, Node> nodeUpdates) {
+        for (Object key : methodCallTargetToCallPath.keySet().toArray()) {
+            if (nodeUpdates.containsKey(key)) {
+                methodCallTargetToCallPath.put(nodeUpdates.get(key), methodCallTargetToCallPath.get(key));
+                methodCallTargetToCallPath.remove(key);
+            }
+        }
+    }
+
+    private StructuredGraph expandInlinableCallNode(OptimizedCallTarget target, Map<Node, TruffleCallPath> methodCallToCallPath, Assumptions assumptions, PhaseContext phaseContext,
+                    MethodCallTargetNode methodCallTargetNode) {
+        ValueNode receiverNode = methodCallTargetNode.receiver();
+        if (receiverNode == null || !receiverNode.isConstant() || !receiverNode.asConstant().getKind().isObject()) {
+            return null;
+        }
+        Object receiverValue = receiverNode.asConstant().asObject();
+        if (!(receiverValue instanceof OptimizedCallNode)) {
+            return null;
+        }
+
+        ResolvedJavaMethod method = methodCallTargetNode.targetMethod();
+        if (!method.getName().equals("call") || method.getSignature().getParameterCount(false) != 2) {
+            return null;
+        }
+        assert method.getSignature().equals(callInlinedMethod.getSignature()) : "Signature of original and inlined method must match.";
+
+        OptimizedCallNode callNode = (OptimizedCallNode) receiverValue;
+        TruffleCallPath callPath = methodCallToCallPath.get(methodCallTargetNode);
+        if (callPath == null) {
+            callPath = new TruffleCallPath(target);
+        }
+        callPath = new TruffleCallPath(callPath, callNode);
+
+        TruffleInliningResult decision = target.getInliningResult();
+        if (decision == null || !decision.isInlined(callPath)) {
+            // the call target decided not to inline this call
+            return null;
+        }
+
+        // inline truffle call -> redirect OptimizedCallNode#ca to OptimizedCallNode#inlinedCall
+        StructuredGraph graph = parseGraph(callInlinedMethod, methodCallTargetNode.arguments(), assumptions, phaseContext);
+
+        methodCallToCallPath.put(methodCallTargetNode, callPath);
+
+        return graph;
+    }
+
     private boolean isFrame(ValueNode receiver) {
         return receiver instanceof NewFrameNode || Objects.equals(ObjectStamp.typeOrNull(receiver.stamp()), frameType);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleCallPath.java	Thu Apr 03 19:06:58 2014 +0200
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ * 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 com.oracle.graal.truffle;
+
+import java.util.*;
+
+import com.oracle.truffle.api.*;
+
+public final class TruffleCallPath implements Iterable<TruffleCallPath>, Comparable<TruffleCallPath> {
+
+    private final OptimizedCallNode callNode;
+    private final TruffleCallPath parent;
+    private final OptimizedCallTarget rootCallTarget;
+
+    public TruffleCallPath(OptimizedCallTarget parent) {
+        this.rootCallTarget = Objects.requireNonNull(parent);
+        this.callNode = null;
+        this.parent = null;
+    }
+
+    public TruffleCallPath(TruffleCallPath parent, OptimizedCallNode node) {
+        this.parent = Objects.requireNonNull(parent);
+        assert !parent.containsPath(this) : "cyclic path detected";
+        this.callNode = Objects.requireNonNull(node);
+        this.rootCallTarget = parent.getRootCallTarget();
+    }
+
+    public boolean isRoot() {
+        return parent == null && rootCallTarget != null;
+    }
+
+    private boolean containsPath(TruffleCallPath p) {
+        return p == this || (getParent() != null && this.getParent().containsPath(p));
+    }
+
+    public OptimizedCallTarget getRootCallTarget() {
+        return rootCallTarget;
+    }
+
+    @Override
+    public String toString() {
+        return (parent != null ? (parent.toString() + " -> ") : "") + getCallTarget().toString();
+    }
+
+    public Iterator<TruffleCallPath> iterator() {
+        return toList().iterator();
+    }
+
+    public OptimizedCallTarget getCallTarget() {
+        return parent == null ? rootCallTarget : callNode.getCurrentCallTarget();
+    }
+
+    public List<TruffleCallPath> toList() {
+        List<TruffleCallPath> list = new ArrayList<>();
+        toListImpl(list);
+        return list;
+    }
+
+    private void toListImpl(List<TruffleCallPath> list) {
+        if (parent != null) {
+            parent.toListImpl(list);
+        }
+        list.add(this);
+    }
+
+    public TruffleCallPath getParent() {
+        return parent;
+    }
+
+    public OptimizedCallNode getCallNode() {
+        return callNode;
+    }
+
+    public int getDepth() {
+        return parent == null ? 0 : (parent.getDepth() + 1);
+    }
+
+    public int compareTo(TruffleCallPath o) {
+        return Objects.compare(this, o, new PathComparator());
+    }
+
+    private static class PathComparator implements Comparator<TruffleCallPath> {
+        public int compare(TruffleCallPath c1, TruffleCallPath c2) {
+            if (c1 == c2) {
+                return 0;
+            }
+
+            Iterator<TruffleCallPath> p1 = c1.toList().iterator();
+            Iterator<TruffleCallPath> p2 = c2.toList().iterator();
+
+            int cmp = 0;
+            while (cmp == 0 && (p1.hasNext() || p2.hasNext())) {
+                TruffleCallPath o1;
+                TruffleCallPath o2;
+                if (p1.hasNext()) {
+                    o1 = p1.next();
+                } else {
+                    return -1;
+                }
+                if (p2.hasNext()) {
+                    o2 = p2.next();
+                } else {
+                    return 1;
+                }
+
+                if (o1 == o2) {
+                    continue;
+                }
+
+                SourceSection s1;
+                if (o1.callNode != null) {
+                    s1 = o1.callNode.getEncapsulatingSourceSection();
+                } else {
+                    s1 = o1.getCallTarget().getRootNode().getSourceSection();
+                }
+
+                SourceSection s2;
+                if (o2.callNode != null) {
+                    s2 = o2.callNode.getEncapsulatingSourceSection();
+                } else {
+                    s2 = o2.getCallTarget().getRootNode().getSourceSection();
+                }
+                cmp = compareSourceSection(s2, s1);
+
+                if (cmp == 0) {
+                    cmp = o1.getCallTarget().toString().compareTo(o2.getCallTarget().toString());
+                }
+            }
+            return cmp;
+        }
+    }
+
+    private static int compareSourceSection(SourceSection s1, SourceSection s2) {
+        return Objects.compare(s1, s2, new Comparator<SourceSection>() {
+            public int compare(SourceSection o1, SourceSection o2) {
+                if (o1 == o2) {
+                    return 0;
+                }
+                if (o1 == null || o2 == null) {
+                    return 0;
+                }
+                int cmp = 0;
+                if (o1.getSource() != null && o2.getSource() != null && !Objects.equals(o1.getSource().getName(), o2.getSource().getName())) {
+                    cmp = o2.getSource().getName().compareTo(o1.getSource().getName());
+                }
+                if (cmp == 0) {
+                    cmp = o2.getStartLine() - o1.getStartLine();
+                }
+                if (cmp == 0) {
+                    cmp = o2.getStartColumn() - o1.getStartColumn();
+                }
+                return cmp;
+            }
+        });
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((callNode == null) ? 0 : callNode.hashCode());
+        result = prime * result + ((parent == null) ? 0 : parent.hashCode());
+        result = prime * result + ((rootCallTarget == null) ? 0 : rootCallTarget.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof TruffleCallPath) {
+            TruffleCallPath other = (TruffleCallPath) obj;
+            if (other.callNode != callNode) {
+                return false;
+            }
+            if (!Objects.equals(other.parent, parent)) {
+                return false;
+            }
+            if (!Objects.equals(other.rootCallTarget, rootCallTarget)) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public TruffleCallPath append(TruffleCallPath path) {
+        if (getCallTarget() != path.getRootCallTarget()) {
+            throw new IllegalArgumentException("Pathes are not compatible and can therfore not be appended.");
+        }
+
+        TruffleCallPath append = this;
+        for (TruffleCallPath childPath : path) {
+            if (!childPath.isRoot()) {
+                append = new TruffleCallPath(append, childPath.getCallNode());
+            }
+        }
+        return append;
+    }
+
+}
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleCompilerImpl.java	Thu Apr 03 18:33:48 2014 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleCompilerImpl.java	Thu Apr 03 19:06:58 2014 +0200
@@ -26,7 +26,6 @@
 import static com.oracle.graal.compiler.GraalCompiler.*;
 import static com.oracle.graal.truffle.TruffleCompilerOptions.*;
 
-import java.io.*;
 import java.util.*;
 import java.util.concurrent.*;
 
@@ -58,8 +57,6 @@
  */
 public class TruffleCompilerImpl implements TruffleCompiler {
 
-    private static final PrintStream OUT = TTY.out().out();
-
     private final Providers providers;
     private final Suites suites;
     private final PartialEvaluator partialEvaluator;
@@ -142,18 +139,16 @@
             OptimizedCallTargetImpl.logOptimizingStart(compilable);
         }
 
-        if (TraceTruffleInliningTree.getValue()) {
-            NodeUtil.printInliningTree(OUT, compilable.getRootNode());
-        }
-
         long timeCompilationStarted = System.nanoTime();
         Assumptions assumptions = new Assumptions(true);
         try (TimerCloseable a = PartialEvaluationTime.start()) {
             graph = partialEvaluator.createGraph(compilable, assumptions);
         }
-        if (Thread.interrupted()) {
+
+        if (Thread.currentThread().isInterrupted()) {
             return null;
         }
+
         long timePartialEvaluationFinished = System.nanoTime();
         int nodeCountPartialEval = graph.getNodeCount();
         InstalledCode compiledMethod = compileMethodHelper(graph, assumptions, compilable.toString(), compilable.getSpeculationLog());
@@ -163,19 +158,24 @@
         if (compiledMethod == null) {
             throw new BailoutException("Could not install method, code cache is full!");
         }
+
         if (!compiledMethod.isValid()) {
             return null;
         }
 
         if (TraceTruffleCompilation.getValue()) {
             byte[] code = compiledMethod.getCode();
+            int calls = OptimizedCallUtils.countCalls(compilable.getInliningResult(), new TruffleCallPath(compilable));
+            int inlinedCalls = (compilable.getInliningResult() != null ? compilable.getInliningResult().size() : 0);
+            int dispatchedCalls = calls - inlinedCalls;
             Map<String, Object> properties = new LinkedHashMap<>();
-            OptimizedCallTarget.addASTSizeProperty(compilable.getRootNode(), properties);
+            OptimizedCallTarget.addASTSizeProperty(compilable.getInliningResult(), new TruffleCallPath(compilable), properties);
             properties.put("Time", String.format("%5.0f(%4.0f+%-4.0f)ms", //
                             (timeCompilationFinished - timeCompilationStarted) / 1e6, //
                             (timePartialEvaluationFinished - timeCompilationStarted) / 1e6, //
                             (timeCompilationFinished - timePartialEvaluationFinished) / 1e6));
-            properties.put("Nodes", String.format("%5d/%5d", nodeCountPartialEval, nodeCountLowered));
+            properties.put("CallNodes", String.format("I %5d/D %5d", inlinedCalls, dispatchedCalls));
+            properties.put("GraalNodes", String.format("%5d/%5d", nodeCountPartialEval, nodeCountLowered));
             properties.put("CodeSize", code != null ? code.length : 0);
             properties.put("Source", formatSourceSection(compilable.getRootNode().getSourceSection()));
 
@@ -222,7 +222,7 @@
 
         result.setAssumptions(newAssumptions);
 
-        InstalledCode installedCode = null;
+        InstalledCode installedCode;
         try (Scope s = Debug.scope("CodeInstall", providers.getCodeCache()); TimerCloseable a = CodeInstallationTime.start()) {
             installedCode = providers.getCodeCache().addMethod(graph.method(), result, speculationLog);
         } catch (Throwable e) {
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleCompilerOptions.java	Thu Apr 03 18:33:48 2014 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleCompilerOptions.java	Thu Apr 03 19:06:58 2014 +0200
@@ -58,7 +58,7 @@
     @Option(help = "Stop inlining if caller's cumulative tree size would exceed this limit")
     public static final OptionValue<Integer> TruffleInliningMaxCallerSize = new OptionValue<>(2250);
     @Option(help = "Skip inlining candidate if its tree size exceeds this limit")
-    public static final OptionValue<Integer> TruffleInliningMaxCalleeSize = new OptionValue<>(350);
+    public static final OptionValue<Integer> TruffleInliningMaxCalleeSize = new OptionValue<>(500);
     @Option(help = "Call frequency relative to call target")
     public static final OptionValue<Double> TruffleInliningMinFrequency = new OptionValue<>(0.3);
     @Option(help = "Allow inlining of less hot candidates if tree size is small")
@@ -100,10 +100,6 @@
     @Option(help = "")
     public static final OptionValue<Boolean> TraceTruffleInlining = new OptionValue<>(false);
     @Option(help = "")
-    public static final OptionValue<Boolean> TraceTruffleInliningTree = new OptionValue<>(false);
-    @Option(help = "")
-    public static final OptionValue<Boolean> TraceTruffleInliningDetails = new OptionValue<>(false);
-    @Option(help = "")
     public static final OptionValue<Boolean> TraceTruffleSplitting = new OptionValue<>(false);
     @Option(help = "")
     public static final OptionValue<Boolean> TruffleCallTargetProfiling = new StableOptionValue<>(false);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningHandler.java	Thu Apr 03 19:06:58 2014 +0200
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ * 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 com.oracle.graal.truffle;
+
+import java.util.*;
+
+import com.oracle.graal.truffle.OptimizedCallUtils.InlinedCallVisitor;
+import com.oracle.truffle.api.nodes.*;
+
+public final class TruffleInliningHandler {
+
+    private final ProfileScoreComparator inliningOrder = new ProfileScoreComparator();
+
+    private final OptimizedCallTarget callTarget;
+    private final TruffleInliningPolicy policy;
+    private final Map<TruffleCallPath, TruffleInliningProfile> profiles;
+    private final Map<OptimizedCallTarget, TruffleInliningResult> inliningResultCache;
+
+    public TruffleInliningHandler(OptimizedCallTarget callTarget, TruffleInliningPolicy policy, Map<OptimizedCallTarget, TruffleInliningResult> inliningResultCache) {
+        this.callTarget = callTarget;
+        this.policy = policy;
+        this.profiles = new HashMap<>();
+        this.inliningResultCache = inliningResultCache;
+    }
+
+    public TruffleInliningResult inline(int originalTotalNodeCount) {
+        Set<TruffleCallPath> inlinedPathes = new HashSet<>();
+        TruffleInliningResult result = new TruffleInliningResult(callTarget, inlinedPathes);
+        TruffleCallPath startPath = new TruffleCallPath(callTarget);
+
+        PriorityQueue<TruffleInliningProfile> queue = new PriorityQueue<>(10, inliningOrder);
+        queueCallSitesForInlining(result, startPath, queue);
+
+        int budget = TruffleCompilerOptions.TruffleInliningMaxCallerSize.getValue() - originalTotalNodeCount;
+        int index = 0;
+        while (!queue.isEmpty()) {
+            TruffleInliningProfile profile = queue.poll();
+            profile.setQueryIndex(index);
+            budget = tryInline(queue, result, inlinedPathes, profile, budget);
+            profiles.put(profile.getCallPath(), profile);
+            index++;
+        }
+        return result;
+    }
+
+    private int tryInline(PriorityQueue<TruffleInliningProfile> queue, TruffleInliningResult result, Set<TruffleCallPath> inlinedPathes, TruffleInliningProfile profile, int budget) {
+
+        if (policy.isAllowed(result, profile, budget)) {
+            int remainingBudget;
+            inlinedPathes.add(profile.getCallPath());
+            if (policy.isAllowedDeep(result, profile, budget)) {
+                if (profile.getRecursiveResult() != null) {
+                    TruffleInliningResult inliningResult = profile.getRecursiveResult();
+                    for (TruffleCallPath recursiveInlinedPath : inliningResult) {
+                        inlinedPathes.add(profile.getCallPath().append(recursiveInlinedPath));
+                    }
+                }
+                remainingBudget = budget - profile.getDeepNodeCount();
+            } else {
+                remainingBudget = budget - profile.getNodeCount();
+            }
+
+            queueCallSitesForInlining(result, profile.getCallPath(), queue);
+            return remainingBudget;
+        }
+
+        return budget;
+    }
+
+    public TruffleInliningPolicy getPolicy() {
+        return policy;
+    }
+
+    public Map<TruffleCallPath, TruffleInliningProfile> getProfiles() {
+        return profiles;
+    }
+
+    private void queueCallSitesForInlining(final TruffleInliningResult currentDecision, TruffleCallPath fromPath, final Collection<TruffleInliningProfile> queue) {
+        fromPath.getCallTarget().getRootNode().accept(new InlinedCallVisitor(currentDecision, fromPath) {
+            @Override
+            public boolean visit(TruffleCallPath path, Node node) {
+                if (node instanceof OptimizedCallNode) {
+                    if (!currentDecision.isInlined(path)) {
+                        addToQueue(queue, currentDecision, path);
+                    }
+                }
+                return true;
+            }
+        });
+    }
+
+    private void addToQueue(final Collection<TruffleInliningProfile> queue, final TruffleInliningResult currentDecision, TruffleCallPath path) {
+        queue.add(lookupProfile(currentDecision, path));
+    }
+
+    public List<TruffleInliningProfile> lookupProfiles(final TruffleInliningResult currentDecision, TruffleCallPath fromPath) {
+        final List<TruffleInliningProfile> pathes = new ArrayList<>();
+        fromPath.getCallTarget().getRootNode().accept(new InlinedCallVisitor(currentDecision, fromPath) {
+            @Override
+            public boolean visit(TruffleCallPath path, Node node) {
+                if (node instanceof OptimizedCallNode) {
+                    pathes.add(lookupProfile(currentDecision, path));
+                }
+                return true;
+            }
+        });
+        return pathes;
+    }
+
+    public TruffleInliningProfile lookupProfile(TruffleInliningResult state, TruffleCallPath callPath) {
+        TruffleInliningProfile profile = profiles.get(callPath);
+        if (profile != null) {
+            return profile;
+        }
+
+        int callSites = callPath.getCallTarget().getKnownCallSiteCount();
+        int nodeCount = OptimizedCallUtils.countNonTrivialNodes(state, callPath);
+        double frequency = calculatePathFrequency(callPath);
+        boolean forced = callPath.getCallNode().isInlined();
+        TruffleInliningResult recursiveResult = lookupChildResult(callPath, nodeCount);
+        int deepNodeCount = OptimizedCallUtils.countNonTrivialNodes(recursiveResult, new TruffleCallPath(callPath.getCallTarget()));
+
+        profile = new TruffleInliningProfile(callPath, callSites, nodeCount, deepNodeCount, frequency, forced, recursiveResult);
+        profile.setScore(policy.calculateScore(profile));
+        profiles.put(callPath, profile);
+
+        return profile;
+    }
+
+    private TruffleInliningResult lookupChildResult(TruffleCallPath callPath, int nodeCount) {
+        OptimizedCallTarget target = callPath.getCallTarget();
+
+        TruffleInliningResult recursiveResult = target.getInliningResult();
+
+        if (recursiveResult == null) {
+            recursiveResult = inliningResultCache.get(callPath.getCallTarget());
+
+            if (recursiveResult == null) {
+                if (inliningResultCache.containsKey(target)) {
+                    // cancel on recursion
+                    return new TruffleInliningResult(target, new HashSet<TruffleCallPath>());
+                }
+                inliningResultCache.put(target, null);
+                TruffleInliningHandler handler = new TruffleInliningHandler(target, policy, inliningResultCache);
+                recursiveResult = handler.inline(nodeCount);
+                inliningResultCache.put(callPath.getCallTarget(), recursiveResult);
+            }
+        }
+        return recursiveResult;
+    }
+
+    private static double calculatePathFrequency(TruffleCallPath callPath) {
+        int parentCallCount = -1;
+        double f = 1.0d;
+        for (TruffleCallPath path : callPath.toList()) {
+            if (parentCallCount != -1) {
+                f *= path.getCallNode().getCallCount() / (double) parentCallCount;
+            }
+            parentCallCount = path.getCallTarget().getCompilationProfile().getCallCount();
+        }
+        return f;
+    }
+
+    private final class ProfileScoreComparator implements Comparator<TruffleInliningProfile> {
+
+        public int compare(TruffleInliningProfile o1, TruffleInliningProfile o2) {
+            return Double.compare(o2.getScore(), o1.getScore());
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningPolicy.java	Thu Apr 03 19:06:58 2014 +0200
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ * 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 com.oracle.graal.truffle;
+
+public interface TruffleInliningPolicy {
+
+    boolean isAllowed(TruffleInliningResult state, TruffleInliningProfile profile, int nodeCountBudget);
+
+    boolean isAllowedDeep(TruffleInliningResult state, TruffleInliningProfile profile, int nodeCountBudget);
+
+    double calculateScore(TruffleInliningProfile profile);
+
+}
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningProfile.java	Thu Apr 03 18:33:48 2014 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningProfile.java	Thu Apr 03 19:06:58 2014 +0200
@@ -24,14 +24,98 @@
 
 import java.util.*;
 
-public interface TruffleInliningProfile extends Comparable<TruffleInliningProfile> {
+public class TruffleInliningProfile implements Comparable<TruffleInliningProfile> {
+
+    private final TruffleCallPath callPath;
+
+    private final int nodeCount;
+    private final int deepNodeCount;
+    private final int callSites;
+    private final double frequency;
+    private final boolean forced;
+    private final TruffleInliningResult recursiveResult;
+
+    private String failedReason;
+    private int queryIndex = -1;
+    private double score;
 
-    OptimizedCallNode getCallNode();
+    public TruffleInliningProfile(TruffleCallPath callPath, int callSites, int nodeCount, int deepNodeCount, double frequency, boolean forced, TruffleInliningResult recursiveResult) {
+        if (callPath.isRoot()) {
+            throw new IllegalArgumentException("Root call path not profilable.");
+        }
+        this.callSites = callSites;
+        this.callPath = callPath;
+        this.nodeCount = nodeCount;
+        this.deepNodeCount = deepNodeCount;
+        this.frequency = frequency;
+        this.forced = forced;
+        this.recursiveResult = recursiveResult;
+    }
 
-    boolean isInliningAllowed();
+    public int getCallSites() {
+        return callSites;
+    }
+
+    public int getNodeCount() {
+        return nodeCount;
+    }
+
+    public TruffleInliningResult getRecursiveResult() {
+        return recursiveResult;
+    }
+
+    public void setScore(double score) {
+        this.score = score;
+    }
+
+    public double getScore() {
+        return score;
+    }
 
-    int compareTo(TruffleInliningProfile o);
+    public String getFailedReason() {
+        return failedReason;
+    }
+
+    public void setQueryIndex(int queryIndex) {
+        this.queryIndex = queryIndex;
+    }
+
+    public int getQueryIndex() {
+        return queryIndex;
+    }
+
+    public void setFailedReason(String reason) {
+        this.failedReason = reason;
+    }
+
+    public boolean isForced() {
+        return forced;
+    }
+
+    public double getFrequency() {
+        return frequency;
+    }
 
-    Map<String, Object> getDebugProperties();
+    public int getDeepNodeCount() {
+        return deepNodeCount;
+    }
+
+    public TruffleCallPath getCallPath() {
+        return callPath;
+    }
+
+    public int compareTo(TruffleInliningProfile o) {
+        return callPath.compareTo(o.callPath);
+    }
 
+    public Map<String, Object> getDebugProperties() {
+        Map<String, Object> properties = new LinkedHashMap<>();
+        properties.put("callSites", callSites);
+        properties.put("nodeCount", nodeCount);
+        properties.put("frequency", frequency);
+        properties.put("score", score);
+        properties.put(String.format("index=%3d, force=%s", queryIndex, (forced ? "Y" : "N")), "");
+        properties.put("reason", failedReason);
+        return properties;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleInliningResult.java	Thu Apr 03 19:06:58 2014 +0200
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ * 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 com.oracle.graal.truffle;
+
+import java.util.*;
+
+public final class TruffleInliningResult implements Iterable<TruffleCallPath> {
+
+    private final OptimizedCallTarget callTarget;
+    private final Set<TruffleCallPath> inlinedPathes;
+
+    public TruffleInliningResult(OptimizedCallTarget callTarget, Set<TruffleCallPath> pathes) {
+        this.callTarget = callTarget;
+        this.inlinedPathes = pathes;
+    }
+
+    public OptimizedCallTarget getCallTarget() {
+        return callTarget;
+    }
+
+    public boolean isInlined(TruffleCallPath path) {
+        return inlinedPathes.contains(path);
+    }
+
+    public int size() {
+        return inlinedPathes.size();
+    }
+
+    public Iterator<TruffleCallPath> iterator() {
+        return Collections.unmodifiableSet(inlinedPathes).iterator();
+    }
+
+    @Override
+    public String toString() {
+        return inlinedPathes.toString();
+    }
+}
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleTreeDumpHandler.java	Thu Apr 03 18:33:48 2014 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/TruffleTreeDumpHandler.java	Thu Apr 03 19:06:58 2014 +0200
@@ -22,9 +22,12 @@
  */
 package com.oracle.graal.truffle;
 
+import java.util.*;
+
 import com.oracle.graal.debug.*;
 import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.nodes.GraphPrintVisitor.ChildSupplier;
 
 public class TruffleTreeDumpHandler implements DebugDumpHandler {
 
@@ -32,10 +35,63 @@
     public void dump(Object object, final String message) {
         if (object instanceof RootCallTarget) {
             RootCallTarget callTarget = (RootCallTarget) object;
-            if (callTarget.getRootNode() != null) {
-                new GraphPrintVisitor().beginGroup(callTarget.toString()).beginGraph(message).visit(callTarget.getRootNode()).printToNetwork(false);
+            dumpRootCallTarget(message, callTarget);
+        }
+    }
+
+    private static void dumpRootCallTarget(final String message, RootCallTarget callTarget) {
+        if (callTarget.getRootNode() != null) {
+            final GraphPrintVisitor visitor = new GraphPrintVisitor();
+
+            final OptimizedCallTarget oct = (OptimizedCallTarget) callTarget;
+
+            visitor.beginGroup(callTarget.toString());
+            dumpInlinedCalls(visitor, oct);
+            dumpFullTree(visitor, message, oct);
+            visitor.printToNetwork(false);
+        }
+    }
+
+    private static void dumpFullTree(final GraphPrintVisitor visitor, final String message, final OptimizedCallTarget oct) {
+        visitor.setChildSupplier(new ChildSupplier() {
+            private TruffleCallPath currentPath = new TruffleCallPath(oct);
+
+            public Object startNode(Object callNode) {
+                if (callNode instanceof OptimizedCallNode) {
+                    currentPath = new TruffleCallPath(currentPath, (OptimizedCallNode) callNode);
+                    if (oct.getInliningResult() != null && oct.getInliningResult().isInlined(currentPath)) {
+                        return ((OptimizedCallNode) callNode).getCurrentRootNode();
+                    }
+                }
+                return null;
             }
-        }
+
+            public void endNode(Object callNode) {
+                if (callNode instanceof OptimizedCallNode) {
+                    currentPath = currentPath.getParent();
+                }
+            }
+        });
+
+        visitor.beginGraph(message).visit(oct.getRootNode());
+        visitor.setChildSupplier(null);
+    }
+
+    private static void dumpInlinedCalls(final GraphPrintVisitor visitor, final OptimizedCallTarget oct) {
+        final Set<OptimizedCallTarget> visitedRoots = new HashSet<>();
+        oct.getRootNode().accept(new OptimizedCallUtils.InlinedCallVisitor(oct.getInliningResult(), new TruffleCallPath(oct)) {
+            @Override
+            public boolean visit(TruffleCallPath path, Node node) {
+                if (node instanceof OptimizedCallNode) {
+                    OptimizedCallTarget target = ((OptimizedCallNode) node).getCurrentCallTarget();
+                    if (!visitedRoots.contains(target)) {
+                        visitor.beginGraph("inlined " + target.toString()).visit(target.getRootNode());
+                        visitedRoots.add(target);
+                    }
+                }
+                return true;
+            }
+        });
     }
 
     public void close() {