EffectsClosure.java revision 12968:4d8a004e5c6d
1/*
2 * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.graalvm.compiler.virtual.phases.ea;
24
25import java.util.ArrayList;
26import java.util.List;
27
28import org.graalvm.compiler.core.common.LocationIdentity;
29import org.graalvm.compiler.core.common.cfg.BlockMap;
30import org.graalvm.compiler.core.common.cfg.Loop;
31import org.graalvm.compiler.core.common.type.Stamp;
32import org.graalvm.compiler.debug.Debug;
33import org.graalvm.compiler.debug.GraalError;
34import org.graalvm.compiler.debug.Indent;
35import org.graalvm.compiler.graph.Node;
36import org.graalvm.compiler.graph.NodeBitMap;
37import org.graalvm.compiler.graph.NodeMap;
38import org.graalvm.compiler.graph.iterators.NodeIterable;
39import org.graalvm.compiler.nodes.AbstractMergeNode;
40import org.graalvm.compiler.nodes.FixedWithNextNode;
41import org.graalvm.compiler.nodes.IfNode;
42import org.graalvm.compiler.nodes.LogicConstantNode;
43import org.graalvm.compiler.nodes.LogicNode;
44import org.graalvm.compiler.nodes.LoopBeginNode;
45import org.graalvm.compiler.nodes.LoopExitNode;
46import org.graalvm.compiler.nodes.PhiNode;
47import org.graalvm.compiler.nodes.ProxyNode;
48import org.graalvm.compiler.nodes.StructuredGraph;
49import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult;
50import org.graalvm.compiler.nodes.ValueNode;
51import org.graalvm.compiler.nodes.ValuePhiNode;
52import org.graalvm.compiler.nodes.cfg.Block;
53import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
54import org.graalvm.compiler.nodes.extended.BoxNode;
55import org.graalvm.compiler.nodes.util.GraphUtil;
56import org.graalvm.compiler.nodes.virtual.AllocatedObjectNode;
57import org.graalvm.compiler.nodes.virtual.CommitAllocationNode;
58import org.graalvm.compiler.nodes.virtual.VirtualObjectNode;
59import org.graalvm.compiler.options.OptionValues;
60import org.graalvm.compiler.phases.graph.ReentrantBlockIterator;
61import org.graalvm.compiler.phases.graph.ReentrantBlockIterator.BlockIteratorClosure;
62import org.graalvm.compiler.phases.graph.ReentrantBlockIterator.LoopInfo;
63import org.graalvm.util.Equivalence;
64import org.graalvm.util.EconomicMap;
65import org.graalvm.util.EconomicSet;
66
67public abstract class EffectsClosure<BlockT extends EffectsBlockState<BlockT>> extends EffectsPhase.Closure<BlockT> {
68
69    protected final ControlFlowGraph cfg;
70    protected final ScheduleResult schedule;
71
72    /**
73     * If a node has an alias, this means that it was replaced with another node during analysis.
74     * Nodes can be replaced by normal ("scalar") nodes, e.g., a LoadIndexedNode with a
75     * ConstantNode, or by virtual nodes, e.g., a NewInstanceNode with a VirtualInstanceNode. A node
76     * was replaced with a virtual value iff the alias is a subclass of VirtualObjectNode.
77     *
78     * This alias map exists only once and is not part of the block state, so that during iterative
79     * loop processing the alias of a node may be changed to another value.
80     */
81    protected final NodeMap<ValueNode> aliases;
82
83    /**
84     * This set allows for a quick check whether a node has inputs that were replaced with "scalar"
85     * values.
86     */
87    private final NodeBitMap hasScalarReplacedInputs;
88
89    /*
90     * TODO: if it was possible to introduce your own subclasses of Block and Loop, these maps would
91     * not be necessary. We could merge the GraphEffectsList logic into them.
92     */
93
94    /**
95     * The effects accumulated during analysis of nodes. They may be cleared and re-filled during
96     * iterative loop processing.
97     */
98    protected final BlockMap<GraphEffectList> blockEffects;
99
100    /**
101     * Effects that can only be applied after the effects from within the loop have been applied and
102     * that must be applied before any effect from after the loop is applied. E.g., updating phis.
103     */
104    protected final EconomicMap<Loop<Block>, GraphEffectList> loopMergeEffects = EconomicMap.create(Equivalence.IDENTITY);
105
106    /**
107     * The entry state of loops is needed when loop proxies are processed.
108     */
109    private final EconomicMap<LoopBeginNode, BlockT> loopEntryStates = EconomicMap.create(Equivalence.IDENTITY);
110
111    // Intended to be used by read-eliminating phases based on the effects phase.
112    protected final EconomicMap<Loop<Block>, LoopKillCache> loopLocationKillCache = EconomicMap.create(Equivalence.IDENTITY);
113
114    protected boolean changed;
115
116    public EffectsClosure(ScheduleResult schedule, ControlFlowGraph cfg) {
117        this.schedule = schedule;
118        this.cfg = cfg;
119        this.aliases = cfg.graph.createNodeMap();
120        this.hasScalarReplacedInputs = cfg.graph.createNodeBitMap();
121        this.blockEffects = new BlockMap<>(cfg);
122        for (Block block : cfg.getBlocks()) {
123            blockEffects.put(block, new GraphEffectList());
124        }
125    }
126
127    @Override
128    public boolean hasChanged() {
129        return changed;
130    }
131
132    @Override
133    public boolean needsApplyEffects() {
134        return true;
135    }
136
137    @Override
138    public void applyEffects() {
139        final StructuredGraph graph = cfg.graph;
140        final ArrayList<Node> obsoleteNodes = new ArrayList<>(0);
141        final ArrayList<GraphEffectList> effectList = new ArrayList<>();
142        /*
143         * Effects are applied during a ordered iteration over the blocks to apply them in the
144         * correct order, e.g., apply the effect that adds a node to the graph before the node is
145         * used.
146         */
147        BlockIteratorClosure<Void> closure = new BlockIteratorClosure<Void>() {
148
149            @Override
150            protected Void getInitialState() {
151                return null;
152            }
153
154            private void apply(GraphEffectList effects) {
155                if (effects != null && !effects.isEmpty()) {
156                    effectList.add(effects);
157                }
158            }
159
160            @Override
161            protected Void processBlock(Block block, Void currentState) {
162                apply(blockEffects.get(block));
163                return currentState;
164            }
165
166            @Override
167            protected Void merge(Block merge, List<Void> states) {
168                return null;
169            }
170
171            @Override
172            protected Void cloneState(Void oldState) {
173                return oldState;
174            }
175
176            @Override
177            protected List<Void> processLoop(Loop<Block> loop, Void initialState) {
178                LoopInfo<Void> info = ReentrantBlockIterator.processLoop(this, loop, initialState);
179                apply(loopMergeEffects.get(loop));
180                return info.exitStates;
181            }
182        };
183        ReentrantBlockIterator.apply(closure, cfg.getStartBlock());
184        for (GraphEffectList effects : effectList) {
185            Debug.log(" ==== effects");
186            effects.apply(graph, obsoleteNodes, false);
187        }
188        /*
189         * Effects that modify the cfg (e.g., removing a branch for an if that got a constant
190         * condition) need to be performed after all other effects, because they change phi value
191         * indexes.
192         */
193        for (GraphEffectList effects : effectList) {
194            Debug.log(" ==== cfg kill effects");
195            effects.apply(graph, obsoleteNodes, true);
196        }
197        Debug.dump(Debug.VERBOSE_LOG_LEVEL, graph, "After applying effects");
198        assert VirtualUtil.assertNonReachable(graph, obsoleteNodes);
199        for (Node node : obsoleteNodes) {
200            if (node.isAlive() && node.hasNoUsages()) {
201                if (node instanceof FixedWithNextNode) {
202                    assert ((FixedWithNextNode) node).next() == null;
203                }
204                node.replaceAtUsages(null);
205                GraphUtil.killWithUnusedFloatingInputs(node);
206            }
207        }
208    }
209
210    @Override
211    protected BlockT processBlock(Block block, BlockT state) {
212        if (!state.isDead()) {
213            GraphEffectList effects = blockEffects.get(block);
214
215            /*
216             * If we enter an if branch that is known to be unreachable, we mark it as dead and
217             * cease to do any more analysis on it. At merges, these dead branches will be ignored.
218             */
219            if (block.getBeginNode().predecessor() instanceof IfNode) {
220                IfNode ifNode = (IfNode) block.getBeginNode().predecessor();
221                LogicNode condition = ifNode.condition();
222                Node alias = getScalarAlias(condition);
223                if (alias instanceof LogicConstantNode) {
224                    LogicConstantNode constant = (LogicConstantNode) alias;
225                    boolean isTrueSuccessor = block.getBeginNode() == ifNode.trueSuccessor();
226
227                    if (constant.getValue() != isTrueSuccessor) {
228                        state.markAsDead();
229                        effects.killIfBranch(ifNode, constant.getValue());
230                        return state;
231                    }
232                }
233            }
234
235            OptionValues options = block.getBeginNode().getOptions();
236            VirtualUtil.trace(options, "\nBlock: %s, preds: %s, succ: %s (", block, block.getPredecessors(), block.getSuccessors());
237
238            // a lastFixedNode is needed in case we want to insert fixed nodes
239            FixedWithNextNode lastFixedNode = null;
240            Iterable<? extends Node> nodes = schedule != null ? schedule.getBlockToNodesMap().get(block) : block.getNodes();
241            for (Node node : nodes) {
242                // reset the aliases (may be non-null due to iterative loop processing)
243                aliases.set(node, null);
244                if (node instanceof LoopExitNode) {
245                    LoopExitNode loopExit = (LoopExitNode) node;
246                    for (ProxyNode proxy : loopExit.proxies()) {
247                        aliases.set(proxy, null);
248                        changed |= processNode(proxy, state, effects, lastFixedNode) && isSignificantNode(node);
249                    }
250                    processLoopExit(loopExit, loopEntryStates.get(loopExit.loopBegin()), state, blockEffects.get(block));
251                }
252                changed |= processNode(node, state, effects, lastFixedNode) && isSignificantNode(node);
253                if (node instanceof FixedWithNextNode) {
254                    lastFixedNode = (FixedWithNextNode) node;
255                }
256                if (state.isDead()) {
257                    break;
258                }
259            }
260            VirtualUtil.trace(options, ")\n    end state: %s\n", state);
261        }
262        return state;
263    }
264
265    /**
266     * Changes to {@link CommitAllocationNode}s, {@link AllocatedObjectNode}s and {@link BoxNode}s
267     * are not considered to be "important". If only changes to those nodes are discovered during
268     * analysis, the effects need not be applied.
269     */
270    private static boolean isSignificantNode(Node node) {
271        return !(node instanceof CommitAllocationNode || node instanceof AllocatedObjectNode || node instanceof BoxNode);
272    }
273
274    /**
275     * Collects the effects of virtualizing the given node.
276     *
277     * @return {@code true} if the effects include removing the node, {@code false} otherwise.
278     */
279    protected abstract boolean processNode(Node node, BlockT state, GraphEffectList effects, FixedWithNextNode lastFixedNode);
280
281    @Override
282    protected BlockT merge(Block merge, List<BlockT> states) {
283        assert blockEffects.get(merge).isEmpty();
284        MergeProcessor processor = createMergeProcessor(merge);
285        doMergeWithoutDead(processor, states);
286        blockEffects.get(merge).addAll(processor.mergeEffects);
287        blockEffects.get(merge).addAll(processor.afterMergeEffects);
288        return processor.newState;
289    }
290
291    @Override
292    @SuppressWarnings("try")
293    protected final List<BlockT> processLoop(Loop<Block> loop, BlockT initialState) {
294        if (initialState.isDead()) {
295            ArrayList<BlockT> states = new ArrayList<>();
296            for (int i = 0; i < loop.getExits().size(); i++) {
297                states.add(initialState);
298            }
299            return states;
300        }
301        /*
302         * Special case nested loops: To avoid an exponential runtime for nested loops we try to
303         * only process them as little times as possible.
304         *
305         * In the first iteration of an outer most loop we go into the inner most loop(s). We run
306         * the first iteration of the inner most loop and then, if necessary, a second iteration.
307         *
308         * We return from the recursion and finish the first iteration of the outermost loop. If we
309         * have to do a second iteration in the outer most loop we go again into the inner most
310         * loop(s) but this time we already know all states that are killed by the loop so inside
311         * the loop we will only have those changes that propagate from the first iteration of the
312         * outer most loop into the current loop. We strip the initial loop state for the inner most
313         * loops and do the first iteration with the (possible) changes from outer loops. If there
314         * are no changes we only have to do 1 iteration and are done.
315         *
316         */
317        BlockT initialStateRemovedKilledLocations = stripKilledLoopLocations(loop, cloneState(initialState));
318        BlockT loopEntryState = initialStateRemovedKilledLocations;
319        BlockT lastMergedState = cloneState(initialStateRemovedKilledLocations);
320        processInitialLoopState(loop, lastMergedState);
321        MergeProcessor mergeProcessor = createMergeProcessor(loop.getHeader());
322        /*
323         * Iterative loop processing: we take the predecessor state as the loop's starting state,
324         * processing the loop contents, merge the states of all loop ends, and check whether the
325         * resulting state is equal to the starting state. If it is, the loop processing has
326         * finished, if not, another iteration is needed.
327         *
328         * This processing converges because the merge processing always makes the starting state
329         * more generic, e.g., adding phis instead of non-phi values.
330         */
331        for (int iteration = 0; iteration < 10; iteration++) {
332            try (Indent i = Debug.logAndIndent("================== Process Loop Effects Closure: block:%s begin node:%s", loop.getHeader(), loop.getHeader().getBeginNode())) {
333                LoopInfo<BlockT> info = ReentrantBlockIterator.processLoop(this, loop, cloneState(lastMergedState));
334
335                List<BlockT> states = new ArrayList<>();
336                states.add(initialStateRemovedKilledLocations);
337                states.addAll(info.endStates);
338                doMergeWithoutDead(mergeProcessor, states);
339
340                Debug.log("MergeProcessor New State: %s", mergeProcessor.newState);
341                Debug.log("===== vs.");
342                Debug.log("Last Merged State: %s", lastMergedState);
343
344                if (mergeProcessor.newState.equivalentTo(lastMergedState)) {
345                    blockEffects.get(loop.getHeader()).insertAll(mergeProcessor.mergeEffects, 0);
346                    loopMergeEffects.put(loop, mergeProcessor.afterMergeEffects);
347
348                    assert info.exitStates.size() == loop.getExits().size();
349                    loopEntryStates.put((LoopBeginNode) loop.getHeader().getBeginNode(), loopEntryState);
350                    assert assertExitStatesNonEmpty(loop, info);
351
352                    processKilledLoopLocations(loop, initialStateRemovedKilledLocations, mergeProcessor.newState);
353                    return info.exitStates;
354                } else {
355                    lastMergedState = mergeProcessor.newState;
356                    for (Block block : loop.getBlocks()) {
357                        blockEffects.get(block).clear();
358                    }
359                }
360            }
361        }
362        throw new GraalError("too many iterations at %s", loop);
363    }
364
365    @SuppressWarnings("unused")
366    protected BlockT stripKilledLoopLocations(Loop<Block> loop, BlockT initialState) {
367        return initialState;
368    }
369
370    @SuppressWarnings("unused")
371    protected void processKilledLoopLocations(Loop<Block> loop, BlockT initialState, BlockT mergedStates) {
372        // nothing to do
373    }
374
375    @SuppressWarnings("unused")
376    protected void processInitialLoopState(Loop<Block> loop, BlockT initialState) {
377        // nothing to do
378    }
379
380    private void doMergeWithoutDead(MergeProcessor mergeProcessor, List<BlockT> states) {
381        int alive = 0;
382        for (BlockT state : states) {
383            if (!state.isDead()) {
384                alive++;
385            }
386        }
387        if (alive == 0) {
388            mergeProcessor.setNewState(states.get(0));
389        } else if (alive == states.size()) {
390            int[] stateIndexes = new int[states.size()];
391            for (int i = 0; i < stateIndexes.length; i++) {
392                stateIndexes[i] = i;
393            }
394            mergeProcessor.setStateIndexes(stateIndexes);
395            mergeProcessor.setNewState(getInitialState());
396            mergeProcessor.merge(states);
397        } else {
398            ArrayList<BlockT> aliveStates = new ArrayList<>(alive);
399            int[] stateIndexes = new int[alive];
400            for (int i = 0; i < states.size(); i++) {
401                if (!states.get(i).isDead()) {
402                    stateIndexes[aliveStates.size()] = i;
403                    aliveStates.add(states.get(i));
404                }
405            }
406            mergeProcessor.setStateIndexes(stateIndexes);
407            mergeProcessor.setNewState(getInitialState());
408            mergeProcessor.merge(aliveStates);
409        }
410    }
411
412    private boolean assertExitStatesNonEmpty(Loop<Block> loop, LoopInfo<BlockT> info) {
413        for (int i = 0; i < loop.getExits().size(); i++) {
414            assert info.exitStates.get(i) != null : "no loop exit state at " + loop.getExits().get(i) + " / " + loop.getHeader();
415        }
416        return true;
417    }
418
419    protected abstract void processLoopExit(LoopExitNode exitNode, BlockT initialState, BlockT exitState, GraphEffectList effects);
420
421    protected abstract MergeProcessor createMergeProcessor(Block merge);
422
423    /**
424     * The main workhorse for merging states, both for loops and for normal merges.
425     */
426    protected abstract class MergeProcessor {
427
428        private final Block mergeBlock;
429        private final AbstractMergeNode merge;
430
431        protected final GraphEffectList mergeEffects;
432        protected final GraphEffectList afterMergeEffects;
433
434        /**
435         * The indexes are used to map from an index in the list of active (non-dead) predecessors
436         * to an index in the list of all predecessors (the latter may be larger).
437         */
438        private int[] stateIndexes;
439        protected BlockT newState;
440
441        public MergeProcessor(Block mergeBlock) {
442            this.mergeBlock = mergeBlock;
443            this.merge = (AbstractMergeNode) mergeBlock.getBeginNode();
444            this.mergeEffects = new GraphEffectList();
445            this.afterMergeEffects = new GraphEffectList();
446        }
447
448        /**
449         * @param states the states that should be merged.
450         */
451        protected abstract void merge(List<BlockT> states);
452
453        private void setNewState(BlockT state) {
454            newState = state;
455            mergeEffects.clear();
456            afterMergeEffects.clear();
457        }
458
459        private void setStateIndexes(int[] stateIndexes) {
460            this.stateIndexes = stateIndexes;
461        }
462
463        protected final Block getPredecessor(int index) {
464            return mergeBlock.getPredecessors()[stateIndexes[index]];
465        }
466
467        protected final NodeIterable<PhiNode> getPhis() {
468            return merge.phis();
469        }
470
471        protected final ValueNode getPhiValueAt(PhiNode phi, int index) {
472            return phi.valueAt(stateIndexes[index]);
473        }
474
475        protected final ValuePhiNode createValuePhi(Stamp stamp) {
476            return new ValuePhiNode(stamp, merge, new ValueNode[mergeBlock.getPredecessorCount()]);
477        }
478
479        protected final void setPhiInput(PhiNode phi, int index, ValueNode value) {
480            afterMergeEffects.initializePhiInput(phi, stateIndexes[index], value);
481        }
482
483        protected final StructuredGraph graph() {
484            return merge.graph();
485        }
486
487        @Override
488        public String toString() {
489            return "MergeProcessor@" + merge;
490        }
491    }
492
493    public void addScalarAlias(ValueNode node, ValueNode alias) {
494        assert !(alias instanceof VirtualObjectNode);
495        aliases.set(node, alias);
496        for (Node usage : node.usages()) {
497            if (!hasScalarReplacedInputs.isNew(usage)) {
498                hasScalarReplacedInputs.mark(usage);
499            }
500        }
501    }
502
503    protected final boolean hasScalarReplacedInputs(Node node) {
504        return hasScalarReplacedInputs.isMarked(node);
505    }
506
507    public ValueNode getScalarAlias(ValueNode node) {
508        assert !(node instanceof VirtualObjectNode);
509        if (node == null || !node.isAlive() || aliases.isNew(node)) {
510            return node;
511        }
512        ValueNode result = aliases.get(node);
513        return (result == null || result instanceof VirtualObjectNode) ? node : result;
514    }
515
516    protected static final class LoopKillCache {
517        private int visits;
518        private LocationIdentity firstLocation;
519        private EconomicSet<LocationIdentity> killedLocations;
520        private boolean killsAll;
521
522        protected LoopKillCache(int visits) {
523            this.visits = visits;
524        }
525
526        protected void visited() {
527            visits++;
528        }
529
530        protected int visits() {
531            return visits;
532        }
533
534        protected void setKillsAll() {
535            killsAll = true;
536            firstLocation = null;
537            killedLocations = null;
538        }
539
540        protected boolean containsLocation(LocationIdentity locationIdentity) {
541            if (killsAll) {
542                return true;
543            }
544            if (firstLocation == null) {
545                return false;
546            }
547            if (!firstLocation.equals(locationIdentity)) {
548                return killedLocations != null ? killedLocations.contains(locationIdentity) : false;
549            }
550            return true;
551        }
552
553        protected void rememberLoopKilledLocation(LocationIdentity locationIdentity) {
554            if (killsAll) {
555                return;
556            }
557            if (firstLocation == null || firstLocation.equals(locationIdentity)) {
558                firstLocation = locationIdentity;
559            } else {
560                if (killedLocations == null) {
561                    killedLocations = EconomicSet.create(Equivalence.IDENTITY);
562                }
563                killedLocations.add(locationIdentity);
564            }
565        }
566
567        protected boolean loopKillsLocations() {
568            if (killsAll) {
569                return true;
570            }
571            return firstLocation != null;
572        }
573    }
574
575}
576