view graal/com.oracle.graal.compiler/src/com/oracle/graal/compiler/phases/EscapeAnalysisPhase.java @ 6286:67a357e3e42a

infrastructure changes in preparation of partial escape analysis
author Lukas Stadler <lukas.stadler@jku.at>
date Fri, 24 Aug 2012 11:45:30 +0200
parents 2fb937396924
children 2de51e692cd8
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.graal.compiler.phases;

import java.util.*;

import com.oracle.graal.api.code.*;
import com.oracle.graal.compiler.*;
import com.oracle.graal.compiler.graph.*;
import com.oracle.graal.compiler.util.*;
import com.oracle.graal.debug.*;
import com.oracle.graal.graph.*;
import com.oracle.graal.nodes.*;
import com.oracle.graal.nodes.PhiNode.PhiType;
import com.oracle.graal.nodes.calc.*;
import com.oracle.graal.nodes.java.*;
import com.oracle.graal.nodes.spi.*;
import com.oracle.graal.nodes.virtual.*;
import com.oracle.max.criutils.*;

public class EscapeAnalysisPhase extends Phase {

    /**
     * Encapsulates the state of the virtual object, which is updated while traversing the control flow graph.
     */
    private static class BlockExitState implements MergeableState<BlockExitState> {

        public final ValueNode[] fieldState;
        public final VirtualObjectNode virtualObject;
        public final Graph graph;

        public BlockExitState(EscapeField[] fields, VirtualObjectNode virtualObject) {
            this.fieldState = new ValueNode[fields.length];
            this.virtualObject = virtualObject;
            this.graph = virtualObject.graph();
            for (int i = 0; i < fields.length; i++) {
                fieldState[i] = ConstantNode.defaultForKind(fields[i].type().kind(), virtualObject.graph());
            }
        }

        public BlockExitState(BlockExitState state) {
            this.fieldState = state.fieldState.clone();
            this.virtualObject = state.virtualObject;
            this.graph = state.graph;
        }

        @Override
        public BlockExitState clone() {
            return new BlockExitState(this);
        }

        @Override
        public boolean merge(MergeNode merge, List<BlockExitState> withStates) {
            PhiNode[] valuePhis = new PhiNode[fieldState.length];
            for (BlockExitState other : withStates) {
                for (int i2 = 0; i2 < fieldState.length; i2++) {
                    if (fieldState[i2] != other.fieldState[i2] && valuePhis[i2] == null) {
                        valuePhis[i2] = graph.add(new PhiNode(fieldState[i2].kind(), merge));
                        valuePhis[i2].addInput(fieldState[i2]);
                        fieldState[i2] = valuePhis[i2];
                    }
                }
            }
            for (BlockExitState other : withStates) {
                for (int i2 = 0; i2 < fieldState.length; i2++) {
                    if (valuePhis[i2] != null) {
                        valuePhis[i2].addInput(other.fieldState[i2]);
                    }
                }
            }
            return true;
        }

        @Override
        public void loopBegin(LoopBeginNode loopBegin) {
            for (int i2 = 0; i2 < fieldState.length; i2++) {
                PhiNode valuePhi = graph.add(new PhiNode(fieldState[i2].kind(), loopBegin));
                valuePhi.addInput(fieldState[i2]);
                fieldState[i2] = valuePhi;
            }
        }

        @Override
        public void loopEnds(LoopBeginNode loopBegin, List<BlockExitState> loopEndStates) {
            for (BlockExitState loopEndState : loopEndStates) {
                for (int i2 = 0; i2 < fieldState.length; i2++) {
                    ((PhiNode) fieldState[i2]).addInput(loopEndState.fieldState[i2]);
                }
            }
        }

        @Override
        public void afterSplit(FixedNode node) {
            // nothing to do...
        }
    }

    private static class EscapementFixup {

        private final Map<Object, Integer> fields = new HashMap<>();
        private final EscapeOp op;
        private final StructuredGraph graph;
        private final FixedWithNextNode node;
        private EscapeField[] escapeFields;
        private final int id;

        public EscapementFixup(int id, EscapeOp op, StructuredGraph graph, FixedWithNextNode node) {
            this.id = id;
            this.op = op;
            this.graph = graph;
            this.node = node;
        }

        public void apply() {
            if (node.usages().isEmpty()) {
                graph.removeFixed(node);
            } else {
                process();
                removeAllocation();
            }
        }

        private void process() {
            for (Node usage : node.usages().snapshot()) {
                op.beforeUpdate(node, usage);
            }
        }

        public void removeAllocation() {
            escapeFields = op.fields(node);
            for (int i = 0; i < escapeFields.length; i++) {
                fields.put(escapeFields[i].representation(), i);
            }
            assert node.objectStamp().isExactType();
            final VirtualObjectNode virtual = graph.add(new VirtualObjectNode(id, node.objectStamp().type(), escapeFields.length));
            if (GraalOptions.TraceEscapeAnalysis || GraalOptions.PrintEscapeAnalysis) {
                TTY.println("new virtual object: " + virtual);
            }
            node.replaceAtUsages(virtual);
            FixedNode next = node.next();
            graph.removeFixed(node);

            List<ValueProxyNode> proxies;
            while (!(proxies = virtual.usages().filter(ValueProxyNode.class).snapshot()).isEmpty()) {
                for (ValueProxyNode vpn : proxies) {
                    assert vpn.value() == virtual;
                    graph.replaceFloating(vpn, virtual);
                }
            }

            if (virtual.fieldsCount() > 0) {
                final BlockExitState startState = new BlockExitState(escapeFields, virtual);
                new PostOrderNodeIterator<BlockExitState>(next, startState) {

                    @Override
                    protected void node(FixedNode curNode) {
                        op.updateState(virtual, curNode, fields, state.fieldState);
                        if (curNode instanceof LoopExitNode) {
                            for (int i = 0; i < state.fieldState.length; i++) {
                                state.fieldState[i] = graph.unique(new ValueProxyNode(state.fieldState[i], (LoopExitNode) curNode, PhiType.Value));
                            }
                        }
                        if (!curNode.isDeleted() && curNode instanceof StateSplit && ((StateSplit) curNode).stateAfter() != null) {
                            VirtualObjectState v = graph.add(new VirtualObjectState(virtual, state.fieldState));
                            ((StateSplit) curNode).stateAfter().addVirtualObjectMapping(v);
                        }
                    }
                }.apply();
            }
        }
    }


    private int virtualIds = 0;


    private final TargetDescription target;
    private final GraalCodeCacheProvider runtime;
    private final Assumptions assumptions;
    private final GraphCache cache;
    private final PhasePlan plan;
    private final OptimisticOptimizations optimisticOpts;

    public EscapeAnalysisPhase(TargetDescription target, GraalCodeCacheProvider runtime, Assumptions assumptions, GraphCache cache, PhasePlan plan, OptimisticOptimizations optimisticOpts) {
        this.runtime = runtime;
        this.target = target;
        this.assumptions = assumptions;
        this.cache = cache;
        this.plan = plan;
        this.optimisticOpts = optimisticOpts;
    }

    private static class EscapeRecord {

        public final Node node;
        public final ArrayList<Node> escapesThrough = new ArrayList<>();
        public final ArrayList<Invoke> invokes = new ArrayList<>();
        public double localWeight;

        public EscapeRecord(Node node) {
            this.node = node;
        }

        public void dump() {
            TTY.print("node %s (%f) escapes through ", node, localWeight);
            for (Node escape : escapesThrough) {
                TTY.print("%s ", escape);
            }
            TTY.println();
        }
    }

    private static Node escape(EscapeRecord record, Node usage) {
        final Node node = record.node;
        if (usage instanceof VirtualState) {
            assert usage.inputs().contains(node);
            return null;
        } else {
            if (usage instanceof FixedNode) {
                record.localWeight += ((FixedNode) usage).probability();
            }
            if (usage instanceof IsNullNode) {
                assert ((IsNullNode) usage).object() == node;
                return null;
            } else if (usage instanceof IsTypeNode) {
                assert ((IsTypeNode) usage).objectClass() == node;
                return null;
            } else if (usage instanceof AccessMonitorNode) {
                assert ((AccessMonitorNode) usage).object() == node;
                return null;
            } else if (usage instanceof LoadFieldNode) {
                assert ((LoadFieldNode) usage).object() == node;
                return null;
            } else if (usage instanceof StoreFieldNode) {
                StoreFieldNode x = (StoreFieldNode) usage;
                // self-references do not escape
                return x.value() == node ? x.object() : null;
            } else if (usage instanceof LoadIndexedNode) {
                LoadIndexedNode x = (LoadIndexedNode) usage;
                if (x.index() == node) {
                    return x.array();
                } else {
                    assert x.array() == node;
                    return EscapeOp.isValidConstantIndex(x) ? null : x.array();
                }
            } else if (usage instanceof StoreIndexedNode) {
                StoreIndexedNode x = (StoreIndexedNode) usage;
                if (x.index() == node) {
                    return x.array();
                } else {
                    assert x.array() == node || x.value() == node;
                    // in order to not escape, the access needs to have a valid constant index and either a store into node or be self-referencing
                    return EscapeOp.isValidConstantIndex(x) && x.value() != node ? null : x.array();
                }
            } else if (usage instanceof RegisterFinalizerNode) {
                assert ((RegisterFinalizerNode) usage).object() == node;
                return null;
            } else if (usage instanceof ArrayLengthNode) {
                assert ((ArrayLengthNode) usage).array() == node;
                return null;
            } else {
                return usage;
            }
        }
    }

    @SuppressWarnings("unused")
    private static void completeAnalysis(StructuredGraph graph) {
        // TODO (lstadler): debugging code

        TTY.println("================================================================");
        for (Node node : graph.getNodes()) {
            if (node != null && node instanceof FixedWithNextNode && node instanceof EscapeAnalyzable) {
                EscapeOp op = ((EscapeAnalyzable) node).getEscapeOp();
                if (op != null && op.canAnalyze(node)) {
                    EscapeRecord record = new EscapeRecord(node);

                    for (Node usage : node.usages()) {
                        Node escapesThrough = escape(record, usage);
                        if (escapesThrough != null && escapesThrough != node) {
                            record.escapesThrough.add(escapesThrough);
                        }
                    }
                    record.dump();
                }
            }
        }
    }


    @Override
    protected void run(StructuredGraph graph) {
        for (Node node : new GraphOrder(graph)) {
            if (node != null && node instanceof FixedWithNextNode && node instanceof EscapeAnalyzable) {
                FixedWithNextNode fixedNode = (FixedWithNextNode) node;
                EscapeOp op = ((EscapeAnalyzable) node).getEscapeOp();
                if (op != null && op.canAnalyze(fixedNode)) {
                    try {
                        performAnalysis(graph, fixedNode, op);
                    } catch (GraalInternalError e) {
                        throw e.addContext("escape analysis of node", node);
                    }
                }
            }
        }
    }

    private void performAnalysis(StructuredGraph graph, FixedWithNextNode node, EscapeOp op) {
        if (!shouldAnalyze(node)) {
            return;
        }
        Set<Node> exits = new HashSet<>();
        Set<Invoke> invokes = new HashSet<>();
        int iterations = 0;

        int minimumWeight = getMinimumWeight(node);
        do {
            double weight = analyze(op, node, exits, invokes);
            if (exits.size() != 0) {
                if (GraalOptions.TraceEscapeAnalysis || GraalOptions.PrintEscapeAnalysis) {
                    TTY.println("%n####### escaping object: %s (%s)", node, node.stamp());
                    if (GraalOptions.TraceEscapeAnalysis) {
                        TTY.print("%d: new value: %s, weight %f, escapes at ", iterations, node, weight);
                        for (Node n : exits) {
                            TTY.print("%s, ", n);
                        }
                        for (Invoke n : invokes) {
                            TTY.print("%s, ", n);
                        }
                        TTY.println();
                    }
                }
                break;
            }
            if (invokes.size() == 0) {

                Debug.dump(graph, "Before escape %s", node);
                Debug.log("!!!!!!!! non-escaping object: %s (%s)", node, node.stamp());
                removeAllocation(graph, node, op);
                Debug.dump(graph, "After escape", graph);
                break;
            }
            if (weight < minimumWeight) {
                if (GraalOptions.TraceEscapeAnalysis || GraalOptions.PrintEscapeAnalysis) {
                    TTY.println("####### possibly escaping object: %s (insufficient weight for inlining: %f)", node, weight);
                }
                break;
            }
            if (!GraalOptions.Inline) {
                break;
            }
            if (GraalOptions.TraceEscapeAnalysis || GraalOptions.PrintEscapeAnalysis) {
                TTY.println("Trying inlining to get a non-escaping object for %s", node);
            }
            new InliningPhase(target, runtime, invokes, assumptions, cache, plan, optimisticOpts).apply(graph);
            new DeadCodeEliminationPhase().apply(graph);
            if (node.isDeleted()) {
                if (GraalOptions.TraceEscapeAnalysis || GraalOptions.PrintEscapeAnalysis) {
                    TTY.println("!!!!!!!! object died while performing escape analysis: %s (%s)", node, node.stamp());
                }
                break;
            }
            exits.clear();
            invokes.clear();
        } while (iterations++ < 3);
    }

    protected void removeAllocation(StructuredGraph graph, FixedWithNextNode node, EscapeOp op) {
        new EscapementFixup(virtualIds++, op, graph, node).apply();

        for (PhiNode phi : node.graph().getNodes(PhiNode.class)) {
            ValueNode simpleValue = phi;
            boolean required = false;
            for (ValueNode value : phi.values()) {
                if (value != phi && value != simpleValue) {
                    if (simpleValue != phi) {
                        required = true;
                        break;
                    }
                    simpleValue = value;
                }
            }
            if (!required) {
                graph.replaceFloating(phi, simpleValue);
            }
        }
        new CanonicalizerPhase(target, runtime, assumptions).apply(graph);
    }

    protected boolean shouldAnalyze(@SuppressWarnings("unused") FixedWithNextNode node) {
        return true;
    }

    protected int getMinimumWeight(@SuppressWarnings("unused") FixedWithNextNode node) {
        return GraalOptions.ForcedInlineEscapeWeight;
    }

    private static double analyze(EscapeOp op, Node node, Collection<Node> exits, Collection<Invoke> invokes) {
        double weight = 0;
        for (Node usage : node.usages().snapshot()) {
            boolean escapes = op.escape(node, usage);
            if (escapes) {
                if (usage instanceof VirtualState) {
                    // nothing to do...
                } else if (usage instanceof ValueProxyNode) {
                    ValueProxyNode proxy = (ValueProxyNode) usage;
                    for (Node proxyUsage : proxy.usages()) {
                        if (!(proxyUsage instanceof VirtualObjectState)) {
                            exits.add(usage);
                            break;
                        }
                    }
                } else if (usage instanceof MethodCallTargetNode) {
                    if (usage.usages().isEmpty()) {
                        usage.safeDelete();
                    } else {
                        invokes.add(((MethodCallTargetNode) usage).invoke());
                    }
                } else {
                    exits.add(usage);
                    break;
                }
            } else {
                if (GraalOptions.ProbabilityAnalysis && usage instanceof FixedNode) {
                    weight += ((FixedNode) usage).probability();
                } else {
                    weight++;
                }
            }
        }
        return weight;
    }

}