view graal/com.oracle.max.graal.compiler/src/com/oracle/max/graal/compiler/phases/InliningPhase.java @ 3529:d683f8a40c05

Second round of refactoring.
author Thomas Wuerthinger <thomas@wuerthinger.net>
date Wed, 10 Aug 2011 00:34:29 +0200
parents 6b841b6b2437
children da773e1b6d9f
line wrap: on
line source

/*
 * Copyright (c) 2011, 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.max.graal.compiler.phases;

import java.lang.reflect.*;
import java.util.*;

import com.oracle.max.graal.compiler.*;
import com.oracle.max.graal.compiler.debug.*;
import com.oracle.max.graal.compiler.graph.*;
import com.oracle.max.graal.compiler.nodes.base.*;
import com.oracle.max.graal.compiler.nodes.base.DeoptimizeNode.DeoptAction;
import com.oracle.max.graal.compiler.nodes.calc.*;
import com.oracle.max.graal.compiler.nodes.java.*;
import com.oracle.max.graal.extensions.*;
import com.oracle.max.graal.graph.*;
import com.sun.cri.bytecode.*;
import com.sun.cri.ci.*;
import com.sun.cri.ri.*;


public class InliningPhase extends Phase {
    /*
     * - Detect method which only call another method with some parameters set to constants: void foo(a) -> void foo(a, b) -> void foo(a, b, c) ...
     *   These should not be taken into account when determining inlining depth.
     */

    public static final HashMap<RiMethod, Integer> methodCount = new HashMap<RiMethod, Integer>();

    private static final int MAX_ITERATIONS = 1000;

    private final GraalCompilation compilation;
    private final IR ir;

    private int inliningSize;
    private final Collection<InvokeNode> hints;

    public InliningPhase(GraalCompilation compilation, IR ir, Collection<InvokeNode> hints) {
        this.compilation = compilation;
        this.ir = ir;
        this.hints = hints;
    }

    private Queue<InvokeNode> newInvokes = new ArrayDeque<InvokeNode>();
    private CompilerGraph graph;

    @Override
    protected void run(Graph graph) {
        this.graph = (CompilerGraph) graph;

        float ratio = GraalOptions.MaximumInlineRatio;
        inliningSize = compilation.method.codeSize();

        if (hints != null) {
            newInvokes.addAll(hints);
        } else {
            for (InvokeNode invoke : graph.getNodes(InvokeNode.class)) {
                newInvokes.add(invoke);
            }
        }

        for (int iterations = 0; iterations < MAX_ITERATIONS; iterations++) {
            Queue<InvokeNode> queue = newInvokes;
            newInvokes = new ArrayDeque<InvokeNode>();
            for (InvokeNode invoke : queue) {
                if (!invoke.isDeleted()) {
                    if (GraalOptions.Meter) {
                        GraalMetrics.InlineConsidered++;
                        if (!invoke.target.hasCompiledCode()) {
                            GraalMetrics.InlineUncompiledConsidered++;
                        }
                    }

                    RiMethod code = inlineInvoke(invoke, iterations, ratio);
                    if (code != null) {
                        if (graph.getNodeCount() > GraalOptions.MaximumInstructionCount) {
                            break;
                        }

                        inlineMethod(invoke, code);
                        if (GraalOptions.TraceInlining) {
                            if (methodCount.get(code) == null) {
                                methodCount.put(code, 1);
                            } else {
                                methodCount.put(code, methodCount.get(code) + 1);
                            }
                        }
                        if (GraalOptions.Meter) {
                            GraalMetrics.InlinePerformed++;
                            if (!invoke.target.hasCompiledCode()) {
                                GraalMetrics.InlineUncompiledPerformed++;
                            }
                        }
                    }
                }
            }
            if (newInvokes.isEmpty()) {
                break;
            }

//            new DeadCodeEliminationPhase().apply(graph);

            ratio *= GraalOptions.MaximumInlineRatio;
        }

        if (GraalOptions.TraceInlining) {
            int inlined = 0;
            int duplicate = 0;
            for (Map.Entry<RiMethod, Integer> entry : methodCount.entrySet()) {
                inlined += entry.getValue();
                duplicate += entry.getValue() - 1;
            }
            if (inlined > 0) {
                TTY.println("overhead: %d (%5.3f %%)", duplicate, duplicate * 100.0 / inlined);
            }
        }
    }

    private RiMethod inlineInvoke(InvokeNode invoke, int iterations, float ratio) {
        RiMethod parent = invoke.stateAfter().method();
        RiTypeProfile profile = parent.typeProfile(invoke.bci);
        if (GraalOptions.Intrinsify) {
            if (GraalOptions.Extend && intrinsicGraph(parent, invoke.bci, invoke.target, invoke.arguments()) != null) {
                return invoke.target;
            }
            if (compilation.runtime.intrinsicGraph(parent, invoke.bci, invoke.target, invoke.arguments()) != null) {
                // Always intrinsify.
                return invoke.target;
            }
        }
        if (!checkInvokeConditions(invoke)) {
            return null;
        }
        if (invoke.opcode() == Bytecodes.INVOKESPECIAL || invoke.target.canBeStaticallyBound()) {
            if (checkTargetConditions(invoke.target, iterations) && checkSizeConditions(parent, iterations, invoke.target, invoke, profile, ratio)) {
                return invoke.target;
            }
            return null;
        }
        if (invoke.receiver().exactType() != null) {
            RiType exact = invoke.receiver().exactType();
            assert exact.isSubtypeOf(invoke.target().holder()) : exact + " subtype of " + invoke.target().holder();
            RiMethod resolved = exact.resolveMethodImpl(invoke.target());
            if (checkTargetConditions(resolved, iterations) && checkSizeConditions(parent, iterations, resolved, invoke, profile, ratio)) {
                return resolved;
            }
            return null;
        }
        RiType holder = invoke.target().holder();

        if (invoke.receiver().declaredType() != null) {
            RiType declared = invoke.receiver().declaredType();
            // the invoke target might be more specific than the holder (happens after inlining: locals lose their declared type...)
            // TODO (ls) fix this
            if (declared.isResolved() && declared.isSubtypeOf(invoke.target().holder())) {
                holder = declared;
            }
        }

        RiMethod concrete = holder.uniqueConcreteMethod(invoke.target);
        if (concrete != null) {
            if (checkTargetConditions(concrete, iterations) && checkSizeConditions(parent, iterations, concrete, invoke, profile, ratio)) {
                if (GraalOptions.TraceInlining) {
                    String targetName = CiUtil.format("%H.%n(%p):%r", invoke.target, false);
                    String concreteName = CiUtil.format("%H.%n(%p):%r", concrete, false);
                    TTY.println("recording concrete method assumption: %s -> %s", targetName, concreteName);
                }
                graph.assumptions().recordConcreteMethod(invoke.target, concrete);
                return concrete;
            }
            return null;
        }
        if (profile != null && profile.probabilities != null && profile.probabilities.length > 0 && profile.morphism == 1) {
            if (GraalOptions.InlineWithTypeCheck) {
                // type check and inlining...
                concrete = profile.types[0].resolveMethodImpl(invoke.target);
                if (concrete != null && checkTargetConditions(concrete, iterations) && checkSizeConditions(parent, iterations, concrete, invoke, profile, ratio)) {
                    IsTypeNode isType = new IsTypeNode(invoke.receiver(), profile.types[0], compilation.graph);
                    FixedGuardNode guard = new FixedGuardNode(isType, graph);
                    assert invoke.predecessor() != null;
                    invoke.predecessor().replaceFirstSuccessor(invoke, guard);
                    guard.setNext(invoke);

                    if (GraalOptions.TraceInlining) {
                        TTY.println("inlining with type check, type probability: %5.3f", profile.probabilities[0]);
                    }
                    return concrete;
                }
                return null;
            } else {
                if (GraalOptions.TraceInlining) {
                    TTY.println("not inlining %s because GraalOptions.InlineWithTypeCheck == false", methodName(invoke.target, invoke));
                }
                return null;
            }
        } else {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because no monomorphic receiver could be found", methodName(invoke.target, invoke));
            }
            return null;
        }
    }

    private String methodName(RiMethod method) {
        return CiUtil.format("%H.%n(%p):%r", method, false) + " (" + method.codeSize() + " bytes)";
    }

    private String methodName(RiMethod method, InvokeNode invoke) {
        if (invoke != null) {
            RiMethod parent = invoke.stateAfter().method();
            return parent.name() + "@" + invoke.bci + ": " + CiUtil.format("%H.%n(%p):%r", method, false) + " (" + method.codeSize() + " bytes)";
        } else {
            return CiUtil.format("%H.%n(%p):%r", method, false) + " (" + method.codeSize() + " bytes)";
        }
    }

    private boolean checkInvokeConditions(InvokeNode invoke) {
        if (!invoke.canInline()) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because the invoke is manually set to be non-inlinable", methodName(invoke.target, invoke));
            }
            return false;
        }
        if (invoke.stateAfter() == null) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because the invoke has no after state", methodName(invoke.target, invoke));
            }
            return false;
        }
        if (invoke.stateAfter().locksSize() > 0) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because of locks", methodName(invoke.target, invoke));
            }
            return false;
        }
        if (!invoke.target.isResolved()) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because the invoke target is unresolved", methodName(invoke.target, invoke));
            }
            return false;
        }
        if (invoke.predecessor() == null) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because the invoke is dead code", methodName(invoke.target, invoke));
            }
            return false;
        }
        if (invoke.stateAfter() == null) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because of missing frame state", methodName(invoke.target, invoke));
            }
        }
        return true;
    }

    private boolean checkTargetConditions(RiMethod method, int iterations) {
        if (!method.isResolved()) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because it is unresolved", methodName(method));
            }
            return false;
        }
        if (Modifier.isNative(method.accessFlags())) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because it is a native method", methodName(method));
            }
            return false;
        }
        if (Modifier.isAbstract(method.accessFlags())) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because it is an abstract method", methodName(method));
            }
            return false;
        }
        if (!method.holder().isInitialized()) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because of non-initialized class", methodName(method));
            }
            return false;
        }
        return true;
    }

    private boolean checkStaticSizeConditions(RiMethod method, InvokeNode invoke) {
        int maximumSize = GraalOptions.MaximumInlineSize;
        if (hints != null && hints.contains(invoke)) {
            maximumSize = GraalOptions.MaximumFreqInlineSize;
        }
        if (method.codeSize() > maximumSize) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because of code size (size: %d, max size: %d)", methodName(method, invoke), method.codeSize(), GraalOptions.MaximumInlineSize);
            }
            return false;
        }
        return true;
    }

    private boolean checkSizeConditions(RiMethod caller, int iterations, RiMethod method, InvokeNode invoke, RiTypeProfile profile, float adjustedRatio) {
        int maximumSize = GraalOptions.MaximumTrivialSize;
        int maximumCompiledSize = GraalOptions.MaximumTrivialCompSize;
        double ratio = 0;
        if (profile != null && profile.count > 0) {
            RiMethod parent = invoke.stateAfter().method();
            if (GraalOptions.ProbabilityAnalysis) {
                ratio = invoke.probability();
            } else {
                ratio = profile.count / (float) parent.invocationCount();
            }
            if (ratio >= GraalOptions.FreqInlineRatio) {
                maximumSize = GraalOptions.MaximumFreqInlineSize;
                maximumCompiledSize = GraalOptions.MaximumFreqInlineCompSize;
            } else if (ratio >= 1 * (1 - adjustedRatio)) {
                maximumSize = GraalOptions.MaximumInlineSize;
                maximumCompiledSize = GraalOptions.MaximumInlineCompSize;
            }
        }
        if (hints != null && hints.contains(invoke)) {
            maximumSize = GraalOptions.MaximumFreqInlineSize;
            maximumCompiledSize = GraalOptions.MaximumFreqInlineCompSize;
        }
        boolean oversize;
        int compiledSize = method.compiledCodeSize();
        if (compiledSize < 0) {
            oversize = (method.codeSize() > maximumSize);
        } else {
            oversize = (compiledSize > maximumCompiledSize);
        }
        if (oversize || iterations >= GraalOptions.MaximumInlineLevel || (method == compilation.method && iterations > GraalOptions.MaximumRecursiveInlineLevel)) {
            if (GraalOptions.TraceInlining) {
                TTY.println("not inlining %s because of size (bytecode: %d, bytecode max: %d, compiled: %d, compiled max: %d, ratio %5.3f, %s) or inlining level",
                                methodName(method, invoke), method.codeSize(), maximumSize, compiledSize, maximumCompiledSize, ratio, profile);
            }
            if (GraalOptions.Extend) {
                boolean newResult = overrideInliningDecision(iterations, caller, invoke.bci, method, false);
                if (GraalOptions.TraceInlining && newResult) {
                    TTY.println("overridden inlining decision");
                }
                return newResult;
            }
            return false;
        }
        if (GraalOptions.TraceInlining) {
            TTY.println("inlining %s (size: %d, max size: %d, ratio %5.3f, %s)", methodName(method, invoke), method.codeSize(), maximumSize, ratio, profile);
        }
        if (GraalOptions.Extend) {
            boolean newResult = overrideInliningDecision(iterations, caller, invoke.bci, method, true);
            if (GraalOptions.TraceInlining && !newResult) {
                TTY.println("overridden inlining decision");
            }
            return newResult;
        }
        return true;
    }

    public static ThreadLocal<ServiceLoader<InliningGuide>> guideLoader = new ThreadLocal<ServiceLoader<InliningGuide>>();

    private boolean overrideInliningDecision(int iteration, RiMethod caller, int bci, RiMethod target, boolean previousDecision) {
        ServiceLoader<InliningGuide> serviceLoader = guideLoader.get();
        if (serviceLoader == null) {
            serviceLoader = ServiceLoader.load(InliningGuide.class);
            guideLoader.set(serviceLoader);
        }

        boolean neverInline = false;
        boolean alwaysInline = false;
        for (InliningGuide guide : serviceLoader) {
            InliningHint hint = guide.getHint(iteration, caller, bci, target);

            if (hint == InliningHint.ALWAYS) {
                alwaysInline = true;
            } else if (hint == InliningHint.NEVER) {
                neverInline = true;
            }
        }

        if (neverInline && alwaysInline) {
            if (GraalOptions.TraceInlining) {
                TTY.println("conflicting inlining hints");
            }
        } else if (neverInline) {
            return false;
        } else if (alwaysInline) {
            return true;
        }
        return previousDecision;
    }


    public static ThreadLocal<ServiceLoader<Intrinsifier>> intrinsicLoader = new ThreadLocal<ServiceLoader<Intrinsifier>>();

    private Graph intrinsicGraph(RiMethod parent, int bci, RiMethod target, List<ValueNode> arguments) {
        ServiceLoader<Intrinsifier> serviceLoader = intrinsicLoader.get();
        if (serviceLoader == null) {
            serviceLoader = ServiceLoader.load(Intrinsifier.class);
            intrinsicLoader.set(serviceLoader);
        }

        for (Intrinsifier intrinsifier : serviceLoader) {
            Graph result = intrinsifier.intrinsicGraph(compilation.runtime, parent, bci, target, arguments);
            if (result != null) {
                return result;
            }
        }
        return null;
    }

    private void inlineMethod(InvokeNode invoke, RiMethod method) {
        RiMethod parent = invoke.stateAfter().method();
        FrameState stateAfter = invoke.stateAfter();
        FixedNode exceptionEdge = invoke.exceptionEdge();
        if (exceptionEdge instanceof PlaceholderNode) {
            exceptionEdge = ((PlaceholderNode) exceptionEdge).next();
        }

        boolean withReceiver = !invoke.isStatic();

        int argumentCount = method.signature().argumentCount(false);
        ValueNode[] parameters = new ValueNode[argumentCount + (withReceiver ? 1 : 0)];
        int slot = withReceiver ? 1 : 0;
        int param = withReceiver ? 1 : 0;
        for (int i = 0; i < argumentCount; i++) {
            parameters[param++] = invoke.arguments().get(slot);
            slot += method.signature().argumentKindAt(i).sizeInSlots();
        }
        if (withReceiver) {
            parameters[0] = invoke.arguments().get(0);
        }

        CompilerGraph graph = null;
        if (GraalOptions.Intrinsify) {
            if (GraalOptions.Extend) {
                graph = (CompilerGraph) intrinsicGraph(parent, invoke.bci, method, invoke.arguments());
            }
            if (graph == null) {
                graph = (CompilerGraph) compilation.runtime.intrinsicGraph(parent, invoke.bci, method, invoke.arguments());
            }
        }
        if (graph != null) {
            if (GraalOptions.TraceInlining) {
                TTY.println("Using intrinsic graph");
            }
        } else {
            graph = GraphBuilderPhase.cachedGraphs.get(method);
        }

        if (graph != null) {
            if (GraalOptions.TraceInlining) {
                TTY.println("Reusing graph for %s", methodName(method, invoke));
            }
        } else {
            if (GraalOptions.TraceInlining) {
                TTY.println("Building graph for %s, locals: %d, stack: %d", methodName(method, invoke), method.maxLocals(), method.maxStackSize());
            }
            graph = new CompilerGraph(null);
            new GraphBuilderPhase(compilation, method, true).apply(graph, true, false);
            if (GraalOptions.ProbabilityAnalysis) {
                new DeadCodeEliminationPhase().apply(graph, true, false);
                new ComputeProbabilityPhase().apply(graph, true, false);
            }
        }

        invoke.clearInputs();

        HashMap<Node, Node> replacements = new HashMap<Node, Node>();
        ArrayList<Node> nodes = new ArrayList<Node>();
        ArrayList<Node> frameStates = new ArrayList<Node>();
        ReturnNode returnNode = null;
        UnwindNode unwindNode = null;
        StartNode startNode = graph.start();
        for (Node node : graph.getNodes()) {
            if (node instanceof StartNode) {
                assert startNode == node;
            } else if (node instanceof LocalNode) {
                replacements.put(node, parameters[((LocalNode) node).index()]);
            } else {
                nodes.add(node);
                if (node instanceof ReturnNode) {
                    returnNode = (ReturnNode) node;
                } else if (node instanceof UnwindNode) {
                    unwindNode = (UnwindNode) node;
                } else if (node instanceof FrameState) {
                    frameStates.add(node);
                }
            }
        }

        if (GraalOptions.TraceInlining) {
            TTY.println("inlining %s: %d frame states, %d nodes", methodName(method), frameStates.size(), nodes.size());
        }

        assert invoke.successors().first() != null : invoke;
        assert invoke.predecessor() != null;
        FixedWithNextNode pred;
        if (withReceiver) {
            pred = new FixedGuardNode(new IsNonNullNode(parameters[0], compilation.graph), compilation.graph);
        } else {
            pred = new PlaceholderNode(compilation.graph);
        }
        invoke.replaceAtPredecessors(pred);
        replacements.put(startNode, pred);

        Map<Node, Node> duplicates = compilation.graph.addDuplicate(nodes, replacements);

        FrameState stateBefore = null;
        double invokeProbability = invoke.probability();
        for (Node node : duplicates.values()) {
            if (GraalOptions.ProbabilityAnalysis) {
                if (node instanceof FixedNode) {
                    FixedNode fixed = (FixedNode) node;
                    fixed.setProbability(fixed.probability() * invokeProbability);
                }
            }
            if (node instanceof InvokeNode && ((InvokeNode) node).canInline()) {
                newInvokes.add((InvokeNode) node);
            } else if (node instanceof FrameState) {
                FrameState frameState = (FrameState) node;
                if (frameState.bci == FrameState.BEFORE_BCI) {
                    if (stateBefore == null) {
                        stateBefore = stateAfter.duplicateModified(invoke.bci, false, invoke.kind, parameters);
                    }
                    frameState.replaceAndDelete(stateBefore);
                } else if (frameState.bci == FrameState.AFTER_BCI) {
                    frameState.replaceAndDelete(stateAfter);
                }
            }
        }

        int monitorIndexDelta = stateAfter.locksSize();
        if (monitorIndexDelta > 0) {
            for (Map.Entry<Node, Node> entry : duplicates.entrySet()) {
                if (entry.getValue() instanceof MonitorAddressNode) {
                    MonitorAddressNode address = (MonitorAddressNode) entry.getValue();
                    address.setMonitorIndex(address.monitorIndex() + monitorIndexDelta);
                }
            }
        }

        if (pred instanceof PlaceholderNode) {
            FixedNode next = ((PlaceholderNode) pred).next();
            ((PlaceholderNode) pred).setNext(null);
            pred.replaceAndDelete(next);
        }

        if (returnNode != null) {
            for (Node usage : invoke.usages().snapshot()) {
                if (returnNode.result() instanceof LocalNode) {
                    usage.replaceFirstInput(invoke, replacements.get(returnNode.result()));
                } else {
                    usage.replaceFirstInput(invoke, duplicates.get(returnNode.result()));
                }
            }
            Node returnDuplicate = duplicates.get(returnNode);
            returnDuplicate.clearInputs();
            Node n = invoke.next();
            invoke.setNext(null);
            returnDuplicate.replaceAndDelete(n);
        }

        if (exceptionEdge != null) {
            if (unwindNode != null) {
                assert unwindNode.predecessor() != null;
                assert exceptionEdge.successors().explicitCount() == 1;
                ExceptionObjectNode obj = (ExceptionObjectNode) exceptionEdge;

                UnwindNode unwindDuplicate = (UnwindNode) duplicates.get(unwindNode);
                for (Node usage : obj.usages().snapshot()) {
                    usage.replaceFirstInput(obj, unwindDuplicate.exception());
                }
                unwindDuplicate.clearInputs();
                Node n = obj.next();
                obj.setNext(null);
                unwindDuplicate.replaceAndDelete(n);
            }
        } else {
            if (unwindNode != null) {
                UnwindNode unwindDuplicate = (UnwindNode) duplicates.get(unwindNode);
                unwindDuplicate.replaceAndDelete(new DeoptimizeNode(DeoptAction.InvalidateRecompile, compilation.graph));
            }
        }

        // adjust all frame states that were copied
        if (frameStates.size() > 0) {
            FrameState outerFrameState = stateAfter.duplicateModified(invoke.bci, stateAfter.rethrowException(), invoke.kind);
            for (Node node : frameStates) {
                FrameState frameState = (FrameState) duplicates.get(node);
                if (!frameState.isDeleted()) {
                    frameState.setOuterFrameState(outerFrameState);
                }
            }
        }

        if (GraalOptions.TraceInlining) {
            ir.printGraph("After inlining " + CiUtil.format("%H.%n(%p):%r", method, false), compilation.graph);
        }
    }
}