001/*
002 * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.lir.constopt;
024
025import java.util.*;
026
027import com.oracle.graal.debug.*;
028import com.oracle.graal.debug.Debug.*;
029
030import com.oracle.graal.compiler.common.cfg.*;
031import com.oracle.graal.lir.constopt.ConstantTree.Flags;
032import com.oracle.graal.lir.constopt.ConstantTree.NodeCost;
033
034/**
035 * Analyzes a {@link ConstantTree} and marks potential materialization positions.
036 */
037public final class ConstantTreeAnalyzer {
038    private final ConstantTree tree;
039    private final BitSet visited;
040
041    public static NodeCost analyze(ConstantTree tree, AbstractBlockBase<?> startBlock) {
042        try (Scope s = Debug.scope("ConstantTreeAnalyzer")) {
043            ConstantTreeAnalyzer analyzer = new ConstantTreeAnalyzer(tree);
044            analyzer.analyzeBlocks(startBlock);
045            return tree.getCost(startBlock);
046        } catch (Throwable e) {
047            throw Debug.handle(e);
048        }
049    }
050
051    private ConstantTreeAnalyzer(ConstantTree tree) {
052        this.tree = tree;
053        this.visited = new BitSet(tree.size());
054    }
055
056    /**
057     * Queues all relevant blocks for {@linkplain #process processing}.
058     *
059     * This is a worklist-style algorithm because a (more elegant) recursive implementation may
060     * cause {@linkplain StackOverflowError stack overflows} on larger graphs.
061     *
062     * @param startBlock The start block of the dominator subtree.
063     */
064    private void analyzeBlocks(AbstractBlockBase<?> startBlock) {
065        Deque<AbstractBlockBase<?>> worklist = new ArrayDeque<>();
066        worklist.offerLast(startBlock);
067        while (!worklist.isEmpty()) {
068            AbstractBlockBase<?> block = worklist.pollLast();
069            try (Indent i = Debug.logAndIndent(3, "analyze: %s", block)) {
070                assert block != null : "worklist is empty!";
071                assert isMarked(block) : "Block not part of the dominator tree: " + block;
072
073                if (isLeafBlock(block)) {
074                    Debug.log(3, "leaf block");
075                    leafCost(block);
076                    continue;
077                }
078
079                if (!visited.get(block.getId())) {
080                    // if not yet visited (and not a leaf block) process all children first!
081                    Debug.log(3, "not marked");
082                    worklist.offerLast(block);
083                    List<? extends AbstractBlockBase<?>> children = block.getDominated();
084                    children.forEach(child -> filteredPush(worklist, child));
085                    visited.set(block.getId());
086                } else {
087                    Debug.log(3, "marked");
088                    // otherwise, process block
089                    process(block);
090                }
091            }
092        }
093    }
094
095    /**
096     * Calculates the cost of a {@code block}. It is assumed that all {@code children} have already
097     * been {@linkplain #process processed}
098     *
099     * @param block The block to be processed.
100     */
101    private void process(AbstractBlockBase<?> block) {
102        List<UseEntry> usages = new ArrayList<>();
103        double bestCost = 0;
104        int numMat = 0;
105        List<? extends AbstractBlockBase<?>> children = block.getDominated();
106        assert children.stream().anyMatch(this::isMarked) : "no children? should have called leafCost(): " + block;
107
108        // collect children costs
109        for (AbstractBlockBase<?> child : children) {
110            if (isMarked(child)) {
111                NodeCost childCost = tree.getCost(child);
112                assert childCost != null : "Child with null cost? block: " + child;
113                usages.addAll(childCost.getUsages());
114                numMat += childCost.getNumMaterializations();
115                bestCost += childCost.getBestCost();
116            }
117        }
118        assert numMat > 0 : "No materialization? " + numMat;
119
120        // choose block
121        List<UseEntry> usagesBlock = tree.getUsages(block);
122        double probabilityBlock = block.probability();
123
124        if (!usagesBlock.isEmpty() || shouldMaterializerInCurrentBlock(probabilityBlock, bestCost, numMat)) {
125            // mark current block as potential materialization position
126            usages.addAll(usagesBlock);
127            bestCost = probabilityBlock;
128            numMat = 1;
129            tree.set(Flags.CANDIDATE, block);
130        } else {
131            // stick with the current solution
132        }
133
134        assert (new HashSet<>(usages)).size() == usages.size() : "doulbe entries? " + usages;
135        NodeCost nodeCost = new NodeCost(bestCost, usages, numMat);
136        tree.setCost(block, nodeCost);
137    }
138
139    /**
140     * This is the cost function that decides whether a materialization should be inserted in the
141     * current block.
142     * <p>
143     * Note that this function does not take into account if a materialization is required despite
144     * the probabilities (e.g. there are usages in the current block).
145     *
146     * @param probabilityBlock Probability of the current block.
147     * @param probabilityChildren Accumulated probability of the children.
148     * @param numMat Number of materializations along the subtrees. We use {@code numMat - 1} to
149     *            insert materializations as late as possible if the probabilities are the same.
150     */
151    private static boolean shouldMaterializerInCurrentBlock(double probabilityBlock, double probabilityChildren, int numMat) {
152        return probabilityBlock * Math.pow(0.9, numMat - 1) < probabilityChildren;
153    }
154
155    private void filteredPush(Deque<AbstractBlockBase<?>> worklist, AbstractBlockBase<?> block) {
156        if (isMarked(block)) {
157            Debug.log(3, "adding %s to the worklist", block);
158            worklist.offerLast(block);
159        }
160    }
161
162    private void leafCost(AbstractBlockBase<?> block) {
163        tree.set(Flags.CANDIDATE, block);
164        tree.getOrInitCost(block);
165    }
166
167    private boolean isMarked(AbstractBlockBase<?> block) {
168        return tree.isMarked(block);
169    }
170
171    private boolean isLeafBlock(AbstractBlockBase<?> block) {
172        return tree.isLeafBlock(block);
173    }
174
175}