Mercurial > hg > truffle
diff graal/com.oracle.max.graal.compiler/src/com/oracle/max/graal/compiler/util/InliningUtil.java @ 3733:e233f5660da4
Added Java files from Maxine project.
author | Thomas Wuerthinger <thomas.wuerthinger@oracle.com> |
---|---|
date | Sat, 17 Dec 2011 19:59:18 +0100 |
parents | |
children | bc8527f3071c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.max.graal.compiler/src/com/oracle/max/graal/compiler/util/InliningUtil.java Sat Dec 17 19:59:18 2011 +0100 @@ -0,0 +1,527 @@ +/* + * 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.util; + +import java.lang.reflect.*; +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.cri.*; +import com.oracle.max.graal.graph.*; +import com.oracle.max.graal.nodes.*; +import com.oracle.max.graal.nodes.DeoptimizeNode.DeoptAction; +import com.oracle.max.graal.nodes.calc.*; +import com.oracle.max.graal.nodes.java.*; +import com.oracle.max.graal.nodes.java.MethodCallTargetNode.InvokeKind; +import com.oracle.max.graal.nodes.util.*; +import com.sun.cri.ci.*; +import com.sun.cri.ri.*; + +public class InliningUtil { + + public interface InliningCallback { + StructuredGraph buildGraph(RiResolvedMethod method); + double inliningWeight(RiResolvedMethod caller, RiResolvedMethod method, Invoke invoke); + void recordConcreteMethodAssumption(RiResolvedMethod method, RiResolvedType context, RiResolvedMethod impl); + } + + public static String methodName(RiResolvedMethod method) { + return CiUtil.format("%H.%n(%p):%r", method, false) + " (" + method.codeSize() + " bytes)"; + } + + private static String methodName(RiResolvedMethod method, Invoke invoke) { + if (invoke != null && invoke.stateAfter() != 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)"; + } + } + + /** + * Represents an opportunity for inlining at the given invoke, with the given weight and level. + * The weight is the amortized weight of the additional code - so smaller is better. + * The level is the number of nested inlinings that lead to this invoke. + */ + public abstract static class InlineInfo implements Comparable<InlineInfo> { + public final Invoke invoke; + public final double weight; + public final int level; + + public InlineInfo(Invoke invoke, double weight, int level) { + this.invoke = invoke; + this.weight = weight; + this.level = level; + } + + @Override + public int compareTo(InlineInfo o) { + return (weight < o.weight) ? -1 : (weight > o.weight) ? 1 : 0; + } + + public abstract boolean canDeopt(); + + /** + * Performs the inlining described by this object and returns the node that represents the return value of the + * inlined method (or null for void methods and methods that have no non-exceptional exit). + * + * @param graph + * @param runtime + * @param callback + * @return The node that represents the return value, or null for void methods and methods that have no + * non-exceptional exit. + */ + public abstract Node inline(StructuredGraph graph, GraalRuntime runtime, InliningCallback callback); + } + + /** + * Represents an inlining opportunity where an intrinsification can take place. Weight and level are always zero. + */ + private static class IntrinsicInlineInfo extends InlineInfo { + public final StructuredGraph intrinsicGraph; + + public IntrinsicInlineInfo(Invoke invoke, StructuredGraph intrinsicGraph) { + super(invoke, 0, 0); + this.intrinsicGraph = intrinsicGraph; + } + + @Override + public Node inline(StructuredGraph compilerGraph, GraalRuntime runtime, InliningCallback callback) { + return InliningUtil.inline(invoke, intrinsicGraph, true); + } + + @Override + public String toString() { + return "intrinsic inlining " + CiUtil.format("%H.%n(%p):%r", invoke.callTarget().targetMethod(), false); + } + + @Override + public boolean canDeopt() { + return false; + } + } + + /** + * Represents an inlining opportunity where the compiler can statically determine a monomorphic target method and + * therefore is able to determine the called method exactly. + */ + private static class ExactInlineInfo extends InlineInfo { + public final RiResolvedMethod concrete; + + public ExactInlineInfo(Invoke invoke, double weight, int level, RiResolvedMethod concrete) { + super(invoke, weight, level); + this.concrete = concrete; + } + + @Override + public Node inline(StructuredGraph compilerGraph, GraalRuntime runtime, InliningCallback callback) { + StructuredGraph graph = GraphBuilderPhase.cachedGraphs.get(concrete); + if (graph != null) { + if (GraalOptions.TraceInlining) { + TTY.println("Reusing graph for %s", methodName(concrete, invoke)); + } + } else { + if (GraalOptions.TraceInlining) { + TTY.println("Building graph for %s, locals: %d, stack: %d", methodName(concrete, invoke), concrete.maxLocals(), concrete.maxStackSize()); + } + graph = callback.buildGraph(concrete); + } + + return InliningUtil.inline(invoke, graph, true); + } + + @Override + public String toString() { + return "exact inlining " + CiUtil.format("%H.%n(%p):%r", concrete, false); + } + + @Override + public boolean canDeopt() { + return false; + } + } + + /** + * Represents an inlining opportunity for which profiling information suggests a monomorphic receiver, but for which + * the receiver type cannot be proven. A type check guard will be generated if this inlining is performed. + */ + private static class TypeGuardInlineInfo extends ExactInlineInfo { + + public final RiResolvedType type; + public final double probability; + + public TypeGuardInlineInfo(Invoke invoke, double weight, int level, RiResolvedMethod concrete, RiResolvedType type, double probability) { + super(invoke, weight, level, concrete); + this.type = type; + this.probability = probability; + } + + @Override + public Node inline(StructuredGraph graph, GraalRuntime runtime, InliningCallback callback) { + IsTypeNode isType = graph.unique(new IsTypeNode(invoke.callTarget().receiver(), type)); + FixedGuardNode guard = graph.add(new FixedGuardNode(isType)); + assert invoke.predecessor() != null; + invoke.predecessor().replaceFirstSuccessor(invoke.node(), guard); + guard.setNext(invoke.node()); + + if (GraalOptions.TraceInlining) { + TTY.println("inlining with type check, type probability: %5.3f", probability); + } + return super.inline(graph, runtime, callback); + } + + @Override + public String toString() { + return "type-checked inlining " + CiUtil.format("%H.%n(%p):%r", concrete, false); + } + + @Override + public boolean canDeopt() { + return true; + } + } + + /** + * Represents an inlining opportunity where the current class hierarchy leads to a monomorphic target method, + * but for which an assumption has to be registered because of non-final classes. + */ + private static class AssumptionInlineInfo extends ExactInlineInfo { + public final RiResolvedType context; + + public AssumptionInlineInfo(Invoke invoke, double weight, int level, RiResolvedType context, RiResolvedMethod concrete) { + super(invoke, weight, level, concrete); + this.context = context; + } + + @Override + public Node inline(StructuredGraph graph, GraalRuntime runtime, InliningCallback callback) { + if (GraalOptions.TraceInlining) { + String targetName = CiUtil.format("%H.%n(%p):%r", invoke.callTarget().targetMethod(), false); + String concreteName = CiUtil.format("%H.%n(%p):%r", concrete, false); + TTY.println("recording concrete method assumption: %s on receiver type %s -> %s", targetName, context, concreteName); + } + callback.recordConcreteMethodAssumption(invoke.callTarget().targetMethod(), context, concrete); + return super.inline(graph, runtime, callback); + } + + @Override + public String toString() { + return "inlining with assumption " + CiUtil.format("%H.%n(%p):%r", concrete, false); + } + + @Override + public boolean canDeopt() { + return true; + } + } + + /** + * Determines if inlining is possible at the given invoke node. + * @param invoke the invoke that should be inlined + * @param level the number of nested inlinings that lead to this invoke, or 0 if the invoke was part of the initial graph + * @param runtime a GraalRuntime instance used to determine of the invoke can be inlined and/or should be intrinsified + * @param callback a callback that is used to determine the weight of a specific inlining + * @return an instance of InlineInfo, or null if no inlining is possible at the given invoke + */ + public static InlineInfo getInlineInfo(Invoke invoke, int level, GraalRuntime runtime, CiAssumptions assumptions, InliningCallback callback) { + if (!checkInvokeConditions(invoke)) { + return null; + } + RiResolvedMethod parent = invoke.stateAfter().method(); + MethodCallTargetNode callTarget = invoke.callTarget(); + + if (callTarget.invokeKind() == InvokeKind.Special || callTarget.targetMethod().canBeStaticallyBound()) { + if (checkTargetConditions(callTarget.targetMethod(), runtime)) { + double weight = callback == null ? 0 : callback.inliningWeight(parent, callTarget.targetMethod(), invoke); + return new ExactInlineInfo(invoke, weight, level, callTarget.targetMethod()); + } + return null; + } + if (callTarget.receiver().exactType() != null) { + RiResolvedType exact = callTarget.receiver().exactType(); + assert exact.isSubtypeOf(callTarget.targetMethod().holder()) : exact + " subtype of " + callTarget.targetMethod().holder(); + RiResolvedMethod resolved = exact.resolveMethodImpl(callTarget.targetMethod()); + if (checkTargetConditions(resolved, runtime)) { + double weight = callback == null ? 0 : callback.inliningWeight(parent, resolved, invoke); + return new ExactInlineInfo(invoke, weight, level, resolved); + } + return null; + } + RiResolvedType holder = callTarget.targetMethod().holder(); + + if (callTarget.receiver().declaredType() != null) { + RiType declared = callTarget.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 instanceof RiResolvedType && ((RiResolvedType) declared).isSubtypeOf(holder)) { + holder = (RiResolvedType) declared; + } + } + // TODO (tw) fix this + if (assumptions == null) { + return null; + } + RiResolvedMethod concrete = holder.uniqueConcreteMethod(callTarget.targetMethod()); + if (concrete != null) { + if (checkTargetConditions(concrete, runtime)) { + double weight = callback == null ? 0 : callback.inliningWeight(parent, concrete, invoke); + return new AssumptionInlineInfo(invoke, weight, level, holder, concrete); + } + return null; + } + RiTypeProfile profile = parent.typeProfile(invoke.bci()); + 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(callTarget.targetMethod()); + if (concrete != null && checkTargetConditions(concrete, runtime)) { + double weight = callback == null ? 0 : callback.inliningWeight(parent, concrete, invoke); + return new TypeGuardInlineInfo(invoke, weight, level, concrete, profile.types[0], profile.probabilities[0]); + } + return null; + } else { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because GraalOptions.InlineWithTypeCheck == false", methodName(callTarget.targetMethod(), invoke)); + } + return null; + } + } else { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because no monomorphic receiver could be found", methodName(callTarget.targetMethod(), invoke)); + } + return null; + } + } + + private static boolean checkInvokeConditions(Invoke invoke) { + if (invoke.stateAfter() == null) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because the invoke has no after state", methodName(invoke.callTarget().targetMethod(), invoke)); + } + return false; + } + if (invoke.stateAfter().locksSize() > 0) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because of locks", methodName(invoke.callTarget().targetMethod(), invoke)); + } + return false; + } + if (invoke.predecessor() == null) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because the invoke is dead code", methodName(invoke.callTarget().targetMethod(), invoke)); + } + return false; + } + if (invoke.stateAfter() == null) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because of missing frame state", methodName(invoke.callTarget().targetMethod(), invoke)); + } + } + return true; + } + + private static boolean checkTargetConditions(RiMethod method, GraalRuntime runtime) { + if (!(method instanceof RiResolvedMethod)) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because it is unresolved", method.toString()); + } + return false; + } + RiResolvedMethod resolvedMethod = (RiResolvedMethod) method; + if (runtime.mustNotInline(resolvedMethod)) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because the CRI set it to be non-inlinable", methodName(resolvedMethod)); + } + return false; + } + if (Modifier.isNative(resolvedMethod.accessFlags())) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because it is a native method", methodName(resolvedMethod)); + } + return false; + } + if (Modifier.isAbstract(resolvedMethod.accessFlags())) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because it is an abstract method", methodName(resolvedMethod)); + } + return false; + } + if (!resolvedMethod.holder().isInitialized()) { + if (GraalOptions.TraceInlining) { + TTY.println("not inlining %s because of non-initialized class", methodName(resolvedMethod)); + } + return false; + } + return true; + } + + /** + * Performs an actual inlining, thereby replacing the given invoke with the given inlineGraph. + * @param invoke the invoke that will be replaced + * @param inlineGraph the graph that the invoke will be replaced with + * @param receiverNullCheck true if a null check needs to be generated for non-static inlinings, false if no such check is required + * @return The node that represents the return value, or null for void methods and methods that have no non-exceptional exit. + */ + public static Node inline(Invoke invoke, StructuredGraph inlineGraph, boolean receiverNullCheck) { + NodeInputList<ValueNode> parameters = invoke.callTarget().arguments(); + Graph graph = invoke.node().graph(); + + FrameState stateAfter = invoke.stateAfter(); + assert stateAfter.isAlive(); + + IdentityHashMap<Node, Node> replacements = new IdentityHashMap<Node, Node>(); + ArrayList<Node> nodes = new ArrayList<Node>(); + ArrayList<Node> frameStates = new ArrayList<Node>(); + ReturnNode returnNode = null; + UnwindNode unwindNode = null; + BeginNode entryPointNode = inlineGraph.start(); + FixedNode firstCFGNode = entryPointNode.next(); + for (Node node : inlineGraph.getNodes()) { + if (node == entryPointNode || node == entryPointNode.stateAfter()) { + // Do nothing. + } else if (node instanceof LocalNode) { + replacements.put(node, parameters.get(((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); + } + } + } + + assert invoke.node().successors().first() != null : invoke; + assert invoke.node().predecessor() != null; + + Map<Node, Node> duplicates = graph.addDuplicates(nodes, replacements); + + FixedNode firstCFGNodeDuplicate = (FixedNode) duplicates.get(firstCFGNode); + FixedNode invokeReplacement; + MethodCallTargetNode callTarget = invoke.callTarget(); + if (callTarget.isStatic() || !receiverNullCheck || parameters.get(0).kind() != CiKind.Object || parameters.get(0).stamp().nonNull()) { + invokeReplacement = firstCFGNodeDuplicate; + } else { + FixedGuardNode guard = graph.add(new FixedGuardNode(graph.unique(new NullCheckNode(parameters.get(0), false)))); + guard.setNext(firstCFGNodeDuplicate); + invokeReplacement = guard; + } + invoke.node().replaceAtPredecessors(invokeReplacement); + + FrameState stateAtExceptionEdge = null; + if (invoke instanceof InvokeWithExceptionNode) { + InvokeWithExceptionNode invokeWithException = ((InvokeWithExceptionNode) invoke); + if (unwindNode != null) { + assert unwindNode.predecessor() != null; + assert invokeWithException.exceptionEdge().successors().explicitCount() == 1; + ExceptionObjectNode obj = (ExceptionObjectNode) invokeWithException.exceptionEdge().next(); + stateAtExceptionEdge = obj.stateAfter(); + 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 { + invokeWithException.killExceptionEdge(); + } + } else { + if (unwindNode != null) { + UnwindNode unwindDuplicate = (UnwindNode) duplicates.get(unwindNode); + unwindDuplicate.replaceAndDelete(graph.add(new DeoptimizeNode(DeoptAction.InvalidateRecompile))); + } + } + + FrameState stateBefore = null; + double invokeProbability = invoke.node().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 FrameState) { + FrameState frameState = (FrameState) node; + if (frameState.bci == FrameState.BEFORE_BCI) { + if (stateBefore == null) { + stateBefore = stateAfter.duplicateModified(invoke.bci(), false, invoke.node().kind(), parameters.toArray(new ValueNode[parameters.size()])); + } + frameState.replaceAndDelete(stateBefore); + } else if (frameState.bci == FrameState.AFTER_BCI) { + frameState.replaceAndDelete(stateAfter); + } else if (frameState.bci == FrameState.AFTER_EXCEPTION_BCI) { + assert stateAtExceptionEdge != null; + frameState.replaceAndDelete(stateAtExceptionEdge); + } + } + } + + Node returnValue = null; + if (returnNode != null) { + if (returnNode.result() instanceof LocalNode) { + returnValue = replacements.get(returnNode.result()); + } else { + returnValue = duplicates.get(returnNode.result()); + } + for (Node usage : invoke.node().usages().snapshot()) { + if (returnNode.result() instanceof LocalNode) { + usage.replaceFirstInput(invoke.node(), returnValue); + } else { + usage.replaceFirstInput(invoke.node(), returnValue); + } + } + Node returnDuplicate = duplicates.get(returnNode); + returnDuplicate.clearInputs(); + Node n = invoke.next(); + invoke.setNext(null); + returnDuplicate.replaceAndDelete(n); + } + + invoke.node().clearInputs(); + invoke.node().replaceAtUsages(null); + GraphUtil.killCFG(invoke.node()); + + // adjust all frame states that were copied + if (frameStates.size() > 0) { + FrameState outerFrameState = stateAfter.duplicateModified(invoke.bci(), stateAfter.rethrowException(), invoke.node().kind()); + for (Node node : frameStates) { + FrameState frameState = (FrameState) duplicates.get(node); + if (!frameState.isDeleted()) { + frameState.setOuterFrameState(outerFrameState); + } + } + } + + if (stateAfter.usages().isEmpty()) { + stateAfter.safeDelete(); + } + return returnValue; + } +}