package org.graalvm.compiler.nodes;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import jdk.internal.vm.compiler.collections.EconomicMap;
import jdk.internal.vm.compiler.collections.Equivalence;
import jdk.internal.vm.compiler.collections.MapCursor;
import jdk.internal.vm.compiler.collections.UnmodifiableEconomicMap;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.graph.Node;
import jdk.vm.ci.meta.MetaUtil;
import jdk.vm.ci.meta.ResolvedJavaMethod;
public class InliningLog {
private static final String TREE_NODE = "\u251c\u2500\u2500";
private static final String LAST_TREE_NODE = "\u2514\u2500\u2500";
public static final class Decision {
private final boolean positive;
private final String reason;
private final String phase;
private final ResolvedJavaMethod target;
private Decision(boolean positive, String reason, String phase, ResolvedJavaMethod target) {
this.positive = positive;
this.reason = reason;
this.phase = phase;
this.target = target;
}
public boolean isPositive() {
return positive;
}
public String getReason() {
return reason;
}
public String getPhase() {
return phase;
}
public ResolvedJavaMethod getTarget() {
return target;
}
@Override
public String toString() {
return String.format("<%s> %s: %s, %s", phase, target != null ? target.format("%H.%n(%p)") : "", positive ? "yes" : "no",
reason);
}
}
private class Callsite {
public final List<Decision> decisions;
public final List<Callsite> children;
public Callsite parent;
public ResolvedJavaMethod target;
public Invokable invoke;
Callsite(Callsite parent, Invokable originalInvoke) {
this.parent = parent;
this.decisions = new ArrayList<>();
this.children = new ArrayList<>();
this.invoke = originalInvoke;
}
public Callsite addChild(Invokable childInvoke) {
Callsite child = new Callsite(this, childInvoke);
children.add(child);
return child;
}
public String positionString() {
if (parent == null) {
if (target != null) {
return "compilation of " + target.format("%H.%n(%p)");
} else if (invoke != null && invoke.getTargetMethod() != null) {
return "compilation of " + invoke.getTargetMethod().getName() + "(bci: " + getBci() + ")";
} else {
return "unknown method (bci: " + getBci() + ")";
}
}
String position;
if (parent.target != null) {
position = MetaUtil.appendLocation(new StringBuilder(100), parent.target, getBci()).toString();
} else if (invoke != null && invoke.getTargetMethod() != null) {
position = invoke.getTargetMethod().getName() + "(bci: " + getBci() + ")";
} else {
position = "unknown method (bci: " + getBci() + ")";
}
return "at " + position;
}
public int getBci() {
return invoke != null ? invoke.bci() : -1;
}
}
private final Callsite root;
private final EconomicMap<Invokable, Callsite> leaves;
private final boolean enabled;
private final DebugContext debug;
public InliningLog(ResolvedJavaMethod rootMethod, boolean enabled, DebugContext debug) {
this.root = new Callsite(null, null);
this.root.target = rootMethod;
this.leaves = EconomicMap.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE);
this.enabled = enabled;
this.debug = debug;
}
public void addDecision(Invokable invoke, boolean positive, String phase, EconomicMap<Node, Node> replacements, InliningLog calleeLog, String reason, Object... args) {
if (debug.hasCompilationListener()) {
String message = String.format(reason, args);
debug.notifyInlining(invoke.getContextMethod(), invoke.getTargetMethod(), positive, message, invoke.bci());
}
if (!enabled) {
return;
}
assert leaves.containsKey(invoke);
assert (!positive && replacements == null && calleeLog == null) || (positive && replacements != null && calleeLog != null) ||
(positive && replacements == null && calleeLog == null);
Callsite callsite = leaves.get(invoke);
callsite.target = callsite.invoke.getTargetMethod();
Decision decision = new Decision(positive, String.format(reason, args), phase, invoke.getTargetMethod());
callsite.decisions.add(decision);
if (positive) {
leaves.removeKey(invoke);
if (calleeLog == null) {
return;
}
EconomicMap<Callsite, Callsite> mapping = EconomicMap.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE);
for (Callsite calleeChild : calleeLog.root.children) {
Callsite child = callsite.addChild(calleeChild.invoke);
copyTree(child, calleeChild, replacements, mapping);
}
MapCursor<Invokable, Callsite> entries = calleeLog.leaves.getEntries();
while (entries.advance()) {
Invokable invokeFromCallee = entries.getKey();
Callsite callsiteFromCallee = entries.getValue();
if (invokeFromCallee.asFixedNode().isDeleted()) {
continue;
}
Invokable inlinedInvokeFromCallee = (Invokable) replacements.get(invokeFromCallee.asFixedNode());
Callsite descendant = mapping.get(callsiteFromCallee);
leaves.put(inlinedInvokeFromCallee, descendant);
}
}
}
public void addLog(UnmodifiableEconomicMap<Node, Node> replacements, InliningLog replacementLog) {
EconomicMap<Callsite, Callsite> mapping = EconomicMap.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE);
for (Callsite calleeChild : replacementLog.root.children) {
Callsite child = root.addChild(calleeChild.invoke);
copyTree(child, calleeChild, replacements, mapping);
}
MapCursor<Invokable, Callsite> entries = replacementLog.leaves.getEntries();
while (entries.advance()) {
Invokable replacementInvoke = entries.getKey();
Callsite replacementCallsite = entries.getValue();
if (replacementInvoke.asFixedNode().isDeleted()) {
continue;
}
Invokable invoke = (Invokable) replacements.get(replacementInvoke.asFixedNode());
Callsite callsite = mapping.get(replacementCallsite);
leaves.put(invoke, callsite);
}
}
public void replaceLog(UnmodifiableEconomicMap<Node, Node> replacements, InliningLog replacementLog) {
assert root.decisions.isEmpty();
assert root.children.isEmpty();
assert leaves.isEmpty();
EconomicMap<Callsite, Callsite> mapping = EconomicMap.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE);
copyTree(root, replacementLog.root, replacements, mapping);
MapCursor<Invokable, Callsite> replacementEntries = replacementLog.leaves.getEntries();
while (replacementEntries.advance()) {
Invokable replacementInvoke = replacementEntries.getKey();
Callsite replacementSite = replacementEntries.getValue();
if (replacementInvoke.isAlive()) {
Invokable invoke = (Invokable) replacements.get((Node) replacementInvoke);
Callsite site = mapping.get(replacementSite);
leaves.put(invoke, site);
}
}
}
private void copyTree(Callsite site, Callsite replacementSite, UnmodifiableEconomicMap<Node, Node> replacements, EconomicMap<Callsite, Callsite> mapping) {
mapping.put(replacementSite, site);
site.target = replacementSite.target;
site.decisions.addAll(replacementSite.decisions);
site.invoke = replacementSite.invoke != null && replacementSite.invoke.isAlive() ? (Invokable) replacements.get(replacementSite.invoke.asFixedNode()) : null;
for (Callsite replacementChild : replacementSite.children) {
Callsite child = new Callsite(site, null);
site.children.add(child);
copyTree(child, replacementChild, replacements, mapping);
}
}
public void checkInvariants(StructuredGraph graph) {
for (Invoke invoke : graph.getInvokes()) {
assert leaves.containsKey(invoke) : "Invoke " + invoke + " not contained in the leaves.";
}
assert root.parent == null;
checkTreeInvariants(root);
}
private void checkTreeInvariants(Callsite site) {
for (Callsite child : site.children) {
assert site == child.parent : "Callsite " + site + " with child " + child + " has an invalid parent pointer " + site;
checkTreeInvariants(child);
}
}
private UpdateScope noUpdates = new UpdateScope((oldNode, newNode) -> {
});
private UpdateScope currentUpdateScope = null;
public final class UpdateScope implements AutoCloseable {
private BiConsumer<Invokable, Invokable> updater;
private UpdateScope(BiConsumer<Invokable, Invokable> updater) {
this.updater = updater;
}
public void activate() {
if (currentUpdateScope != null) {
throw GraalError.shouldNotReachHere("InliningLog updating already set.");
}
currentUpdateScope = this;
}
@Override
public void close() {
if (enabled) {
assert currentUpdateScope != null;
currentUpdateScope = null;
}
}
public BiConsumer<Invokable, Invokable> getUpdater() {
return updater;
}
}
public BiConsumer<Invokable, Invokable> getUpdateScope() {
if (currentUpdateScope == null) {
return null;
}
return currentUpdateScope.getUpdater();
}
public UpdateScope openUpdateScope(BiConsumer<Invokable, Invokable> updater) {
if (enabled) {
UpdateScope scope = new UpdateScope(updater);
scope.activate();
return scope;
} else {
return null;
}
}
public UpdateScope openDefaultUpdateScope() {
if (enabled) {
noUpdates.activate();
return noUpdates;
} else {
return null;
}
}
private RootScope currentRootScope = null;
public final class RootScope implements AutoCloseable {
private final RootScope parent;
private Callsite replacementRoot;
public RootScope(RootScope parent, Callsite replacementRoot) {
this.parent = parent;
this.replacementRoot = replacementRoot;
}
void activate() {
currentRootScope = this;
}
public Invokable getInvoke() {
return replacementRoot.invoke;
}
@Override
public void close() {
if (enabled) {
assert currentRootScope != null;
removeLeafCallsite(replacementRoot.invoke);
currentRootScope = parent;
}
}
}
public static final class PlaceholderInvokable implements Invokable {
private final int bci;
private final ResolvedJavaMethod callerMethod;
private final ResolvedJavaMethod method;
public PlaceholderInvokable(ResolvedJavaMethod callerMethod, ResolvedJavaMethod method, int bci) {
this.callerMethod = callerMethod;
this.method = method;
this.bci = bci;
}
@Override
public ResolvedJavaMethod getTargetMethod() {
return method;
}
@Override
public int bci() {
return bci;
}
@Override
public void setBci(int bci) {
GraalError.shouldNotReachHere();
}
@Override
public boolean isAlive() {
return false;
}
@Override
public FixedNode asFixedNode() {
throw new UnsupportedOperationException("Parsed invokable is a placeholder, not a concrete node.");
}
@Override
public ResolvedJavaMethod getContextMethod() {
return callerMethod;
}
}
public RootScope openRootScope(ResolvedJavaMethod callerMethod, ResolvedJavaMethod target, int bci) {
return openRootScope(new PlaceholderInvokable(callerMethod, target, bci));
}
public RootScope openRootScope(Invokable invoke) {
if (enabled) {
if (!leaves.containsKey(invoke)) {
trackNewCallsite(invoke);
}
RootScope scope = new RootScope(currentRootScope, leaves.get(invoke));
scope.replacementRoot.target = invoke.getTargetMethod();
scope.activate();
return scope;
} else {
return null;
}
}
public boolean containsLeafCallsite(Invokable invokable) {
return leaves.containsKey(invokable);
}
public void removeLeafCallsite(Invokable invokable) {
leaves.removeKey(invokable);
}
public void trackNewCallsite(Invokable invoke) {
assert !leaves.containsKey(invoke);
Callsite currentRoot = findCurrentRoot();
Callsite callsite = new Callsite(currentRoot, invoke);
currentRoot.children.add(callsite);
leaves.put(invoke, callsite);
}
private Callsite findCurrentRoot() {
return currentRootScope != null ? currentRootScope.replacementRoot : root;
}
public void trackDuplicatedCallsite(Invokable sibling, Invokable newInvoke) {
Callsite siblingCallsite = leaves.get(sibling);
Callsite parentCallsite = siblingCallsite.parent;
Callsite callsite = parentCallsite.addChild(newInvoke);
leaves.put(newInvoke, callsite);
}
public void updateExistingCallsite(Invokable previousInvoke, Invokable newInvoke) {
Callsite callsite = leaves.get(previousInvoke);
leaves.removeKey(previousInvoke);
leaves.put(newInvoke, callsite);
callsite.invoke = newInvoke;
}
public String formatAsTree(boolean nullIfEmpty) {
assert root.decisions.isEmpty();
assert !root.children.isEmpty() || leaves.isEmpty();
if (nullIfEmpty && root.children.isEmpty()) {
return null;
}
StringBuilder builder = new StringBuilder(512);
formatAsTree(root, "", builder);
return builder.toString();
}
private void formatAsTree(Callsite site, String indent, StringBuilder builder) {
String position = site.positionString();
builder.append(indent).append(position).append(": ");
if (site.decisions.isEmpty()) {
if (site.parent != null) {
builder.append("(no decisions made about ").append(site.target != null ? site.target.format("%H.%n(%p)") : "").append(")");
}
builder.append(System.lineSeparator());
} else if (site.decisions.size() == 1) {
builder.append(site.decisions.get(0).toString());
builder.append(System.lineSeparator());
} else {
builder.append(System.lineSeparator());
for (Decision decision : site.decisions) {
String node = (decision == site.decisions.get(site.decisions.size() - 1)) ? LAST_TREE_NODE : TREE_NODE;
builder.append(indent + " " + node).append(decision.toString());
builder.append(System.lineSeparator());
}
}
for (Callsite child : site.children) {
formatAsTree(child, indent + " ", builder);
}
}
}