--- old/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/cfg/Loop.java 2019-03-09 03:55:58.704612003 +0100 +++ new/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/cfg/Loop.java 2019-03-09 03:55:58.340609428 +0100 @@ -25,19 +25,28 @@ package org.graalvm.compiler.core.common.cfg; +import static org.graalvm.compiler.core.common.cfg.AbstractBlockBase.BLOCK_ID_COMPARATOR; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; public abstract class Loop> { private final Loop parent; - private final List> children; + private final ArrayList> children; private final int depth; private final int index; private final T header; - private final List blocks; - private final List exits; + private final ArrayList blocks; + private final ArrayList exits; + /** + * Natural exits, ignoring LoopExitNodes. + * + * @see #getNaturalExits() + */ + private final ArrayList naturalExits; protected Loop(Loop parent, int index, T header) { this.parent = parent; @@ -51,6 +60,7 @@ this.blocks = new ArrayList<>(); this.children = new ArrayList<>(); this.exits = new ArrayList<>(); + this.naturalExits = new ArrayList<>(); } public abstract long numBackedges(); @@ -84,12 +94,87 @@ return blocks; } - public List getExits() { + /** + * Returns the loop exits. + * + * This might be a conservative set: before framestate assignment it matches the LoopExitNodes + * even if earlier blocks could be considered as exits. After framestate assignments, this is + * the same as {@link #getNaturalExits()}. + * + *

+ * LoopExitNodes are inserted in the control-flow during parsing and are natural exits: they are + * the earliest block at which we are guaranteed to have exited the loop. However, after some + * transformations of the graph, the natural exit might go up but the LoopExitNodes are not + * updated. + *

+ * + *

+ * For example in: + * + *

+     * for (int i = 0; i < N; i++) {
+     *     if (c) {
+     *         // Block 1
+     *         if (dummy) {
+     *             // ...
+     *         } else {
+     *             // ...
+     *         }
+     *         if (b) {
+     *             continue;
+     *         } else {
+     *             // Block 2
+     *             // LoopExitNode
+     *             break;
+     *         }
+     *     }
+     * }
+     * 
+ * + * After parsing, the natural exits match the LoopExitNode: Block 2 is a natural exit and has a + * LoopExitNode. If the {@code b} condition gets canonicalized to {@code false}, the natural + * exit moves to Block 1 while the LoopExitNode remains in Block 2. In such a scenario, + * {@code getLoopExits()} will contain block 2 while {@link #getNaturalExits()} will contain + * block 1. + * + * + * @see #getNaturalExits() + */ + public List getLoopExits() { return exits; } - public void addExit(T t) { - exits.add(t); + public boolean isLoopExit(T block) { + assert isSorted(exits); + return Collections.binarySearch(exits, block, BLOCK_ID_COMPARATOR) >= 0; + } + + /** + * Returns the natural exit points: these are the earliest block that are guaranteed to never + * reach a back-edge. + * + * This can not be used in the context of preserving or using loop-closed form. + * + * @see #getLoopExits() + */ + public ArrayList getNaturalExits() { + return naturalExits; + } + + public boolean isNaturalExit(T block) { + assert isSorted(naturalExits); + return Collections.binarySearch(naturalExits, block, BLOCK_ID_COMPARATOR) >= 0; + } + + private static > boolean isSorted(List list) { + int lastId = Integer.MIN_VALUE; + for (AbstractBlockBase block : list) { + if (block.getId() < lastId) { + return false; + } + lastId = block.getId(); + } + return true; } /** @@ -115,4 +200,9 @@ public int hashCode() { return index + depth * 31; } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } }