src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/InliningLog.java
author iveresov
Fri, 02 Feb 2018 17:28:17 -0800
changeset 48861 47f19ff9903c
child 49451 e06f9607f370
permissions -rw-r--r--
8194819: Update Graal Reviewed-by: kvn

/*
 * Copyright (c) 2018, 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 org.graalvm.compiler.nodes;

import jdk.vm.ci.code.BytecodePosition;
import jdk.vm.ci.meta.ResolvedJavaMethod;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class contains all inlining decisions performed on a graph during the compilation.
 *
 * Each inlining decision consists of:
 *
 * <ul>
 * <li>a value indicating whether the decision was positive or negative</li>
 * <li>the call target method</li>
 * <li>the reason for the inlining decision</li>
 * <li>the name of the phase in which the inlining decision took place</li>
 * <li>the special {@link BytecodePositionWithId} value that describes the position in the bytecode
 * together with the callsite-specific unique identifier</li>
 * <li>the inlining log of the inlined graph, or {@code null} if the decision was negative</li>
 * </ul>
 *
 * A phase that does inlining should use the instance of this class contained in the
 * {@link StructuredGraph} by calling {@link #addDecision} whenever it decides to inline a method.
 * If there are invokes in the graph at the end of the respective phase, then that phase must call
 * {@link #addDecision} to log negative decisions.
 *
 * At the end of the compilation, the contents of the inlining log can be converted into a list of
 * decisions by calling {@link #formatAsList} or into an inlining tree, by calling
 * {@link #formatAsTree}.
 */
public class InliningLog {
    /**
     * A bytecode position with a unique identifier attached.
     *
     * The purpose of this class is to disambiguate callsites that are duplicated by a
     * transformation (such as loop peeling or path duplication).
     */
    public static final class BytecodePositionWithId extends BytecodePosition implements Comparable<BytecodePositionWithId> {
        private final int id;

        public BytecodePositionWithId(BytecodePositionWithId caller, ResolvedJavaMethod method, int bci, int id) {
            super(caller, method, bci);
            this.id = id;
        }

        public BytecodePositionWithId addCallerWithId(BytecodePositionWithId caller) {
            if (getCaller() == null) {
                return new BytecodePositionWithId(caller, getMethod(), getBCI(), id);
            } else {
                return new BytecodePositionWithId(getCaller().addCallerWithId(caller), getMethod(), getBCI(), id);
            }
        }

        public static BytecodePositionWithId create(FrameState state) {
            return create(state, true);
        }

        @SuppressWarnings("deprecation")
        private static BytecodePositionWithId create(FrameState state, boolean topLevel) {
            if (state == null) {
                return null;
            }
            ResolvedJavaMethod method = state.getMethod();
            int bci = topLevel ? state.bci - 3 : state.bci;
            int id = state.getId();
            return new BytecodePositionWithId(create(state.outerFrameState(), false), method, bci, id);
        }

        @Override
        public BytecodePositionWithId getCaller() {
            return (BytecodePositionWithId) super.getCaller();
        }

        public BytecodePositionWithId withoutCaller() {
            return new BytecodePositionWithId(null, getMethod(), getBCI(), id);
        }

        public long getId() {
            return id;
        }

        @Override
        public boolean equals(Object that) {
            return super.equals(that) && this.id == ((BytecodePositionWithId) that).id;
        }

        @Override
        public int hashCode() {
            return super.hashCode() ^ (id << 16);
        }

        @Override
        public int compareTo(BytecodePositionWithId that) {
            int diff = this.getBCI() - that.getBCI();
            if (diff != 0) {
                return diff;
            }
            diff = (int) (this.getId() - that.getId());
            return diff;
        }
    }

    public static final class Decision {
        private final boolean positive;
        private final String reason;
        private final String phase;
        private final ResolvedJavaMethod target;
        private final BytecodePositionWithId position;
        private final InliningLog childLog;

        private Decision(boolean positive, String reason, String phase, ResolvedJavaMethod target, BytecodePositionWithId position, InliningLog childLog) {
            assert position != null;
            this.positive = positive;
            this.reason = reason;
            this.phase = phase;
            this.target = target;
            this.position = position;
            this.childLog = childLog;
        }

        public boolean isPositive() {
            return positive;
        }

        public String getReason() {
            return reason;
        }

        public String getPhase() {
            return phase;
        }

        public BytecodePositionWithId getPosition() {
            return position;
        }

        public InliningLog getChildLog() {
            return childLog;
        }

        public ResolvedJavaMethod getTarget() {
            return target;
        }
    }

    private static class Callsite {
        public final List<String> decisions;
        public final Map<BytecodePositionWithId, Callsite> children;
        public final BytecodePositionWithId position;

        Callsite(BytecodePositionWithId position) {
            this.children = new HashMap<>();
            this.position = position;
            this.decisions = new ArrayList<>();
        }

        public Callsite getOrCreateChild(BytecodePositionWithId fromRootPosition) {
            Callsite child = children.get(fromRootPosition.withoutCaller());
            if (child == null) {
                child = new Callsite(fromRootPosition);
                children.put(fromRootPosition.withoutCaller(), child);
            }
            return child;
        }

        public Callsite createCallsite(BytecodePositionWithId fromRootPosition, String decision) {
            Callsite parent = getOrCreateCallsite(fromRootPosition.getCaller());
            Callsite callsite = parent.getOrCreateChild(fromRootPosition);
            callsite.decisions.add(decision);
            return null;
        }

        private Callsite getOrCreateCallsite(BytecodePositionWithId fromRootPosition) {
            if (fromRootPosition == null) {
                return this;
            } else {
                Callsite parent = getOrCreateCallsite(fromRootPosition.getCaller());
                Callsite callsite = parent.getOrCreateChild(fromRootPosition);
                return callsite;
            }
        }
    }

    private final List<Decision> decisions;

    public InliningLog() {
        this.decisions = new ArrayList<>();
    }

    public List<Decision> getDecisions() {
        return decisions;
    }

    public void addDecision(boolean positive, String reason, String phase, ResolvedJavaMethod target, BytecodePositionWithId position,
                    InliningLog calleeLog) {
        Decision decision = new Decision(positive, reason, phase, target, position, calleeLog);
        decisions.add(decision);
    }

    public String formatAsList() {
        StringBuilder builder = new StringBuilder();
        formatAsList("", null, decisions, builder);
        return builder.toString();
    }

    private void formatAsList(String phasePrefix, BytecodePositionWithId caller, List<Decision> subDecisions, StringBuilder builder) {
        for (Decision decision : subDecisions) {
            String phaseStack = phasePrefix.equals("") ? decision.getPhase() : phasePrefix + "-" + decision.getPhase();
            String target = decision.getTarget().format("%H.%n(%p)");
            String positive = decision.isPositive() ? "inline" : "do not inline";
            BytecodePositionWithId absolutePosition = decision.getPosition().addCallerWithId(caller);
            String position = "  " + decision.getPosition().toString().replaceAll("\n", "\n  ");
            String line = String.format("<%s> %s %s: %s\n%s", phaseStack, positive, target, decision.getReason(), position);
            builder.append(line).append(System.lineSeparator());
            if (decision.getChildLog() != null) {
                formatAsList(phaseStack, absolutePosition, decision.getChildLog().getDecisions(), builder);
            }
        }
    }

    public String formatAsTree() {
        Callsite root = new Callsite(null);
        createTree("", null, root, decisions);
        StringBuilder builder = new StringBuilder();
        formatAsTree(root, "", builder);
        return builder.toString();
    }

    private void createTree(String phasePrefix, BytecodePositionWithId caller, Callsite root, List<Decision> subDecisions) {
        for (Decision decision : subDecisions) {
            String phaseStack = phasePrefix.equals("") ? decision.getPhase() : phasePrefix + "-" + decision.getPhase();
            String target = decision.getTarget().format("%H.%n(%p)");
            BytecodePositionWithId absolutePosition = decision.getPosition().addCallerWithId(caller);
            String line = String.format("<%s> %s: %s", phaseStack, target, decision.getReason());
            root.createCallsite(absolutePosition, line);
            if (decision.getChildLog() != null) {
                createTree(phaseStack, absolutePosition, root, decision.getChildLog().getDecisions());
            }
        }
    }

    private void formatAsTree(Callsite site, String indent, StringBuilder builder) {
        String position = site.position != null ? site.position.withoutCaller().toString() : "<root>";
        String decision = String.join("; ", site.decisions);
        String line = String.format("%s%s; %s", indent, position, decision);
        builder.append(line).append(System.lineSeparator());
        String childIndent = indent + "  ";
        site.children.entrySet().stream().sorted((x, y) -> x.getKey().compareTo(y.getKey())).forEach(e -> {
            formatAsTree(e.getValue(), childIndent, builder);
        });
    }
}