/* * 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.util.*; import com.oracle.max.criutils.*; import com.oracle.max.graal.compiler.*; import com.oracle.max.graal.compiler.graphbuilder.*; import com.oracle.max.graal.compiler.phases.PhasePlan.PhasePosition; import com.oracle.max.graal.compiler.util.*; import com.oracle.max.graal.compiler.util.InliningUtil.InlineInfo; import com.oracle.max.graal.compiler.util.InliningUtil.InliningCallback; import com.oracle.max.graal.cri.*; import com.oracle.max.graal.extensions.*; import com.oracle.max.graal.graph.*; import com.oracle.max.graal.nodes.*; import com.sun.cri.ci.*; import com.sun.cri.ri.*; public class InliningPhase extends Phase implements InliningCallback { /* * - 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. * - honor the result of overrideInliningDecision(0, caller, invoke.bci, method, true); */ private static final int MAX_ITERATIONS = 1000; private final CiTarget target; private final GraalRuntime runtime; private int inliningSize; private final Collection<Invoke> hints; private final PriorityQueue<InlineInfo> inlineCandidates = new PriorityQueue<InlineInfo>(); private NodeMap<InlineInfo> inlineInfos; private StructuredGraph graph; private CiAssumptions assumptions; private final PhasePlan plan; private final GraphBuilderConfiguration config; public InliningPhase(CiTarget target, GraalRuntime runtime, Collection<Invoke> hints, CiAssumptions assumptions, PhasePlan plan) { this(target, runtime, hints, assumptions, plan, GraphBuilderConfiguration.getDefault(plan)); } public InliningPhase(CiTarget target, GraalRuntime runtime, Collection<Invoke> hints, CiAssumptions assumptions, PhasePlan plan, GraphBuilderConfiguration config) { this.target = target; this.runtime = runtime; this.hints = hints; this.assumptions = assumptions; this.plan = plan; this.config = config; } @SuppressWarnings("unchecked") @Override protected void run(StructuredGraph graph) { this.graph = graph; inlineInfos = graph.createNodeMap(); if (hints != null) { Iterable<? extends Node> hints = Util.uncheckedCast(this.hints); scanInvokes(hints, 0); } else { scanInvokes(graph.getNodes(InvokeNode.class), 0); scanInvokes(graph.getNodes(InvokeWithExceptionNode.class), 0); } while (!inlineCandidates.isEmpty() && graph.getNodeCount() < GraalOptions.MaximumDesiredSize) { InlineInfo info = inlineCandidates.remove(); double penalty = Math.pow(GraalOptions.InliningSizePenaltyExp, graph.getNodeCount() / (double) GraalOptions.MaximumDesiredSize) / GraalOptions.InliningSizePenaltyExp; if (info.weight > GraalOptions.MaximumInlineWeight / (1 + penalty * GraalOptions.InliningSizePenalty)) { if (GraalOptions.TraceInlining) { TTY.println("not inlining (cut off by weight):"); while (info != null) { TTY.println(" %f %s", info.weight, info); info = inlineCandidates.poll(); } } return; } Iterable<Node> newNodes = null; if (info.invoke.node().isAlive()) { try { info.inline(this.graph, runtime, this); if (GraalOptions.TraceInlining) { TTY.println("inlining %f: %s", info.weight, info); } if (GraalOptions.TraceInlining) { context.observable.fireCompilationEvent("after inlining " + info, graph); } // get the new nodes here, the canonicalizer phase will reset the mark newNodes = graph.getNewNodes(); if (GraalOptions.OptCanonicalizer) { new CanonicalizerPhase(target, runtime, true, assumptions).apply(graph); } if (GraalOptions.Intrinsify) { new IntrinsificationPhase(runtime).apply(graph, context); } if (GraalOptions.Meter) { context.metrics.InlinePerformed++; } } catch (CiBailout bailout) { // TODO determine if we should really bail out of the whole compilation. throw bailout; } catch (AssertionError e) { throw new GraalInternalError(e).addContext(info.toString()); } catch (RuntimeException e) { throw new GraalInternalError(e).addContext(info.toString()); } catch (GraalInternalError e) { throw e.addContext(info.toString()); } } if (newNodes != null && info.level <= GraalOptions.MaximumInlineLevel) { scanInvokes(newNodes, info.level + 1); } } } private void scanInvokes(Iterable<? extends Node> newNodes, int level) { graph.mark(); for (Node node : newNodes) { if (node != null) { if (node instanceof Invoke) { Invoke invoke = (Invoke) node; scanInvoke(invoke, level); } for (Node usage : node.usages().snapshot()) { if (usage instanceof Invoke) { Invoke invoke = (Invoke) usage; scanInvoke(invoke, level); } } } } } private void scanInvoke(Invoke invoke, int level) { InlineInfo info = InliningUtil.getInlineInfo(invoke, level, runtime, assumptions, this); if (info != null) { if (GraalOptions.Meter) { context.metrics.InlineConsidered++; } inlineCandidates.add(info); } } public static final Map<RiMethod, Integer> parsedMethods = new HashMap<RiMethod, Integer>(); @Override public StructuredGraph buildGraph(RiResolvedMethod method) { StructuredGraph graph = new StructuredGraph(); new GraphBuilderPhase(runtime, method, null, config).apply(graph, context, true, false); if (plan != null) { plan.runPhases(PhasePosition.AFTER_PARSING, graph, context); } if (GraalOptions.ProbabilityAnalysis) { new DeadCodeEliminationPhase().apply(graph, context, true, false); new ComputeProbabilityPhase().apply(graph, context, true, false); } new CanonicalizerPhase(target, runtime, assumptions).apply(graph, context, true, false); return graph; } @Override public double inliningWeight(RiResolvedMethod caller, RiResolvedMethod method, Invoke invoke) { double ratio; if (hints != null && hints.contains(invoke)) { ratio = 1000000; } else { if (GraalOptions.ProbabilityAnalysis) { ratio = invoke.node().probability(); } else { RiTypeProfile profile = caller.typeProfile(invoke.bci()); if (profile != null && profile.count > 0) { RiResolvedMethod parent = invoke.stateAfter().method(); ratio = profile.count / (float) parent.invocationCount(); } else { ratio = 1; } } } final double normalSize; // TODO(ls) get rid of this magic, it's here to emulate the old behavior for the time being if (ratio < 0.01) { ratio = 0.01; } if (ratio < 0.5) { normalSize = 10 * ratio / 0.5; } else if (ratio < 2) { normalSize = 10 + (35 - 10) * (ratio - 0.5) / 1.5; } else if (ratio < 20) { normalSize = 35; } else if (ratio < 40) { normalSize = 35 + (350 - 35) * (ratio - 20) / 20; } else { normalSize = 350; } int count; if (GraalOptions.ParseBeforeInlining) { if (!parsedMethods.containsKey(method)) { StructuredGraph graph = new StructuredGraph(); new GraphBuilderPhase(runtime, method, null).apply(graph, context, true, false); new CanonicalizerPhase(target, runtime, assumptions).apply(graph, context, true, false); count = graphComplexity(graph); parsedMethods.put(method, count); } else { count = parsedMethods.get(method); } } else { count = method.codeSize(); } return count / normalSize; } public static int graphComplexity(StructuredGraph graph) { int result = 0; for (Node node : graph.getNodes()) { if (node instanceof ConstantNode || node instanceof LocalNode || node instanceof BeginNode || node instanceof ReturnNode || node instanceof UnwindNode) { result += 0; } else if (node instanceof PhiNode) { result += 5; } else if (node instanceof MergeNode || node instanceof Invoke || node instanceof LoopEndNode || node instanceof EndNode) { result += 0; } else if (node instanceof ControlSplitNode) { result += ((ControlSplitNode) node).blockSuccessorCount(); } else { result += 1; } } // ReturnNode ret = graph.getReturn(); // if (ret != null && ret.result() != null) { // if (ret.result().kind() == CiKind.Object && ret.result().exactType() != null) { // result -= 5; // } // } return Math.max(1, result); } 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; } @Override public void recordConcreteMethodAssumption(RiResolvedMethod method, RiResolvedType context, RiResolvedMethod impl) { assumptions.recordConcreteMethod(method, context, impl); } }