| /* |
| * Copyright (c) 2011, 2016, 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.phases.schedule; |
| |
| import static jdk.internal.vm.compiler.collections.Equivalence.IDENTITY; |
| import static org.graalvm.compiler.core.common.GraalOptions.GuardPriorities; |
| import static org.graalvm.compiler.core.common.GraalOptions.OptScheduleOutOfLoops; |
| import static org.graalvm.compiler.core.common.cfg.AbstractControlFlowGraph.strictlyDominates; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.EnumMap; |
| import java.util.Formatter; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| import java.util.function.Function; |
| |
| import jdk.internal.vm.compiler.collections.EconomicSet; |
| import org.graalvm.compiler.core.common.SuppressFBWarnings; |
| import org.graalvm.compiler.core.common.cfg.AbstractControlFlowGraph; |
| import org.graalvm.compiler.core.common.cfg.BlockMap; |
| import org.graalvm.compiler.debug.Assertions; |
| import org.graalvm.compiler.graph.Graph.NodeEvent; |
| import org.graalvm.compiler.graph.Graph.NodeEventListener; |
| import org.graalvm.compiler.graph.Graph.NodeEventScope; |
| import org.graalvm.compiler.graph.Node; |
| import org.graalvm.compiler.graph.NodeBitMap; |
| import org.graalvm.compiler.graph.NodeMap; |
| import org.graalvm.compiler.graph.NodeStack; |
| import org.graalvm.compiler.nodes.AbstractBeginNode; |
| import org.graalvm.compiler.nodes.AbstractEndNode; |
| import org.graalvm.compiler.nodes.AbstractMergeNode; |
| import org.graalvm.compiler.nodes.ControlSinkNode; |
| import org.graalvm.compiler.nodes.ControlSplitNode; |
| import org.graalvm.compiler.nodes.DeoptimizeNode; |
| import org.graalvm.compiler.nodes.FixedNode; |
| import org.graalvm.compiler.nodes.GuardNode; |
| import org.graalvm.compiler.nodes.IfNode; |
| import org.graalvm.compiler.nodes.KillingBeginNode; |
| import org.graalvm.compiler.nodes.LoopBeginNode; |
| import org.graalvm.compiler.nodes.LoopExitNode; |
| import org.graalvm.compiler.nodes.PhiNode; |
| import org.graalvm.compiler.nodes.ProxyNode; |
| import org.graalvm.compiler.nodes.StartNode; |
| import org.graalvm.compiler.nodes.StaticDeoptimizingNode; |
| import org.graalvm.compiler.nodes.StaticDeoptimizingNode.GuardPriority; |
| import org.graalvm.compiler.nodes.StructuredGraph; |
| import org.graalvm.compiler.nodes.StructuredGraph.GuardsStage; |
| import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult; |
| import org.graalvm.compiler.nodes.ValueNode; |
| import org.graalvm.compiler.nodes.VirtualState; |
| import org.graalvm.compiler.nodes.calc.ConvertNode; |
| import org.graalvm.compiler.nodes.calc.IsNullNode; |
| import org.graalvm.compiler.nodes.cfg.Block; |
| import org.graalvm.compiler.nodes.cfg.ControlFlowGraph; |
| import org.graalvm.compiler.nodes.cfg.HIRLoop; |
| import org.graalvm.compiler.nodes.cfg.LocationSet; |
| import org.graalvm.compiler.nodes.memory.FloatingReadNode; |
| import org.graalvm.compiler.nodes.memory.MemoryCheckpoint; |
| import org.graalvm.compiler.nodes.spi.ValueProxy; |
| import org.graalvm.compiler.options.OptionValues; |
| import org.graalvm.compiler.phases.Phase; |
| import jdk.internal.vm.compiler.word.LocationIdentity; |
| |
| public final class SchedulePhase extends Phase { |
| |
| public enum SchedulingStrategy { |
| EARLIEST_WITH_GUARD_ORDER, |
| EARLIEST, |
| LATEST, |
| LATEST_OUT_OF_LOOPS, |
| FINAL_SCHEDULE; |
| |
| public boolean isEarliest() { |
| return this == EARLIEST || this == EARLIEST_WITH_GUARD_ORDER; |
| } |
| |
| public boolean isLatest() { |
| return !isEarliest(); |
| } |
| } |
| |
| private final SchedulingStrategy selectedStrategy; |
| |
| private final boolean immutableGraph; |
| |
| public SchedulePhase(OptionValues options) { |
| this(false, options); |
| } |
| |
| public SchedulePhase(boolean immutableGraph, OptionValues options) { |
| this(OptScheduleOutOfLoops.getValue(options) ? SchedulingStrategy.LATEST_OUT_OF_LOOPS : SchedulingStrategy.LATEST, immutableGraph); |
| } |
| |
| public SchedulePhase(SchedulingStrategy strategy) { |
| this(strategy, false); |
| } |
| |
| public SchedulePhase(SchedulingStrategy strategy, boolean immutableGraph) { |
| this.selectedStrategy = strategy; |
| this.immutableGraph = immutableGraph; |
| } |
| |
| private NodeEventScope verifyImmutableGraph(StructuredGraph graph) { |
| if (immutableGraph && Assertions.assertionsEnabled()) { |
| return graph.trackNodeEvents(new NodeEventListener() { |
| @Override |
| public void changed(NodeEvent e, Node node) { |
| assert false : "graph changed: " + e + " on node " + node; |
| } |
| }); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| @SuppressWarnings("try") |
| protected void run(StructuredGraph graph) { |
| try (NodeEventScope scope = verifyImmutableGraph(graph)) { |
| Instance inst = new Instance(); |
| inst.run(graph, selectedStrategy, immutableGraph); |
| } |
| } |
| |
| public static void run(StructuredGraph graph, SchedulingStrategy strategy, ControlFlowGraph cfg) { |
| Instance inst = new Instance(cfg); |
| inst.run(graph, strategy, false); |
| } |
| |
| public static class Instance { |
| |
| private static final double IMPLICIT_NULL_CHECK_OPPORTUNITY_PROBABILITY_FACTOR = 2; |
| /** |
| * Map from blocks to the nodes in each block. |
| */ |
| protected ControlFlowGraph cfg; |
| protected BlockMap<List<Node>> blockToNodesMap; |
| protected NodeMap<Block> nodeToBlockMap; |
| |
| public Instance() { |
| this(null); |
| } |
| |
| public Instance(ControlFlowGraph cfg) { |
| this.cfg = cfg; |
| } |
| |
| @SuppressWarnings("try") |
| public void run(StructuredGraph graph, SchedulingStrategy selectedStrategy, boolean immutableGraph) { |
| // assert GraphOrder.assertNonCyclicGraph(graph); |
| |
| if (this.cfg == null) { |
| this.cfg = ControlFlowGraph.compute(graph, true, true, true, false); |
| } |
| |
| NodeMap<Block> currentNodeMap = graph.createNodeMap(); |
| NodeBitMap visited = graph.createNodeBitMap(); |
| BlockMap<List<Node>> earliestBlockToNodesMap = new BlockMap<>(cfg); |
| this.nodeToBlockMap = currentNodeMap; |
| this.blockToNodesMap = earliestBlockToNodesMap; |
| |
| scheduleEarliestIterative(earliestBlockToNodesMap, currentNodeMap, visited, graph, immutableGraph, selectedStrategy == SchedulingStrategy.EARLIEST_WITH_GUARD_ORDER); |
| |
| if (!selectedStrategy.isEarliest()) { |
| // For non-earliest schedules, we need to do a second pass. |
| BlockMap<List<Node>> latestBlockToNodesMap = new BlockMap<>(cfg); |
| for (Block b : cfg.getBlocks()) { |
| latestBlockToNodesMap.put(b, new ArrayList<>()); |
| } |
| |
| BlockMap<ArrayList<FloatingReadNode>> watchListMap = calcLatestBlocks(selectedStrategy, currentNodeMap, earliestBlockToNodesMap, visited, latestBlockToNodesMap, immutableGraph); |
| sortNodesLatestWithinBlock(cfg, earliestBlockToNodesMap, latestBlockToNodesMap, currentNodeMap, watchListMap, visited); |
| |
| assert verifySchedule(cfg, latestBlockToNodesMap, currentNodeMap); |
| assert (!Assertions.detailedAssertionsEnabled(graph.getOptions())) || MemoryScheduleVerification.check(cfg.getStartBlock(), latestBlockToNodesMap); |
| |
| this.blockToNodesMap = latestBlockToNodesMap; |
| |
| } |
| cfg.setNodeToBlock(currentNodeMap); |
| |
| graph.setLastSchedule(new ScheduleResult(this.cfg, this.nodeToBlockMap, this.blockToNodesMap)); |
| } |
| |
| @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE", justification = "false positive found by findbugs") |
| private BlockMap<ArrayList<FloatingReadNode>> calcLatestBlocks(SchedulingStrategy strategy, NodeMap<Block> currentNodeMap, BlockMap<List<Node>> earliestBlockToNodesMap, NodeBitMap visited, |
| BlockMap<List<Node>> latestBlockToNodesMap, boolean immutableGraph) { |
| BlockMap<ArrayList<FloatingReadNode>> watchListMap = new BlockMap<>(cfg); |
| Block[] reversePostOrder = cfg.reversePostOrder(); |
| for (int j = reversePostOrder.length - 1; j >= 0; --j) { |
| Block currentBlock = reversePostOrder[j]; |
| List<Node> blockToNodes = earliestBlockToNodesMap.get(currentBlock); |
| LocationSet killed = null; |
| int previousIndex = blockToNodes.size(); |
| for (int i = blockToNodes.size() - 1; i >= 0; --i) { |
| Node currentNode = blockToNodes.get(i); |
| assert currentNodeMap.get(currentNode) == currentBlock; |
| assert !(currentNode instanceof PhiNode) && !(currentNode instanceof ProxyNode); |
| assert visited.isMarked(currentNode); |
| if (currentNode instanceof FixedNode) { |
| // For these nodes, the earliest is at the same time the latest block. |
| } else { |
| Block latestBlock = null; |
| |
| LocationIdentity constrainingLocation = null; |
| if (currentNode instanceof FloatingReadNode) { |
| // We are scheduling a floating read node => check memory |
| // anti-dependencies. |
| FloatingReadNode floatingReadNode = (FloatingReadNode) currentNode; |
| LocationIdentity location = floatingReadNode.getLocationIdentity(); |
| if (location.isMutable()) { |
| // Location can be killed. |
| constrainingLocation = location; |
| if (currentBlock.canKill(location)) { |
| if (killed == null) { |
| killed = new LocationSet(); |
| } |
| fillKillSet(killed, blockToNodes.subList(i + 1, previousIndex)); |
| previousIndex = i; |
| if (killed.contains(location)) { |
| // Earliest block kills location => we need to stay within |
| // earliest block. |
| latestBlock = currentBlock; |
| } |
| } |
| } |
| } |
| |
| if (latestBlock == null) { |
| // We are not constraint within earliest block => calculate optimized |
| // schedule. |
| calcLatestBlock(currentBlock, strategy, currentNode, currentNodeMap, constrainingLocation, watchListMap, latestBlockToNodesMap, visited, immutableGraph); |
| } else { |
| selectLatestBlock(currentNode, currentBlock, latestBlock, currentNodeMap, watchListMap, constrainingLocation, latestBlockToNodesMap); |
| } |
| } |
| } |
| } |
| return watchListMap; |
| } |
| |
| protected static void selectLatestBlock(Node currentNode, Block currentBlock, Block latestBlock, NodeMap<Block> currentNodeMap, BlockMap<ArrayList<FloatingReadNode>> watchListMap, |
| LocationIdentity constrainingLocation, BlockMap<List<Node>> latestBlockToNodesMap) { |
| |
| assert checkLatestEarliestRelation(currentNode, currentBlock, latestBlock); |
| if (currentBlock != latestBlock) { |
| |
| currentNodeMap.setAndGrow(currentNode, latestBlock); |
| |
| if (constrainingLocation != null && latestBlock.canKill(constrainingLocation)) { |
| if (watchListMap.get(latestBlock) == null) { |
| watchListMap.put(latestBlock, new ArrayList<>()); |
| } |
| watchListMap.get(latestBlock).add((FloatingReadNode) currentNode); |
| } |
| } |
| |
| latestBlockToNodesMap.get(latestBlock).add(currentNode); |
| } |
| |
| private static boolean checkLatestEarliestRelation(Node currentNode, Block earliestBlock, Block latestBlock) { |
| assert AbstractControlFlowGraph.dominates(earliestBlock, latestBlock) || (currentNode instanceof VirtualState && latestBlock == earliestBlock.getDominator()) : String.format( |
| "%s %s (%s) %s (%s)", currentNode, earliestBlock, earliestBlock.getBeginNode(), latestBlock, latestBlock.getBeginNode()); |
| return true; |
| } |
| |
| private static boolean verifySchedule(ControlFlowGraph cfg, BlockMap<List<Node>> blockToNodesMap, NodeMap<Block> nodeMap) { |
| for (Block b : cfg.getBlocks()) { |
| List<Node> nodes = blockToNodesMap.get(b); |
| for (Node n : nodes) { |
| assert n.isAlive(); |
| assert nodeMap.get(n) == b; |
| StructuredGraph g = (StructuredGraph) n.graph(); |
| if (g.hasLoops() && g.getGuardsStage() == GuardsStage.AFTER_FSA && n instanceof DeoptimizeNode) { |
| assert b.getLoopDepth() == 0 : n; |
| } |
| } |
| } |
| return true; |
| } |
| |
| public static Block checkKillsBetween(Block earliestBlock, Block latestBlock, LocationIdentity location) { |
| assert strictlyDominates(earliestBlock, latestBlock); |
| Block current = latestBlock.getDominator(); |
| |
| // Collect dominator chain that needs checking. |
| List<Block> dominatorChain = new ArrayList<>(); |
| dominatorChain.add(latestBlock); |
| while (current != earliestBlock) { |
| // Current is an intermediate dominator between earliestBlock and latestBlock. |
| assert strictlyDominates(earliestBlock, current) && strictlyDominates(current, latestBlock); |
| if (current.canKill(location)) { |
| dominatorChain.clear(); |
| } |
| dominatorChain.add(current); |
| current = current.getDominator(); |
| } |
| |
| // The first element of dominatorChain now contains the latest possible block. |
| assert dominatorChain.size() >= 1; |
| assert dominatorChain.get(dominatorChain.size() - 1).getDominator() == earliestBlock; |
| |
| Block lastBlock = earliestBlock; |
| for (int i = dominatorChain.size() - 1; i >= 0; --i) { |
| Block currentBlock = dominatorChain.get(i); |
| if (currentBlock.getLoopDepth() > lastBlock.getLoopDepth()) { |
| // We are entering a loop boundary. The new loops must not kill the location for |
| // the crossing to be safe. |
| if (currentBlock.getLoop() != null && ((HIRLoop) currentBlock.getLoop()).canKill(location)) { |
| break; |
| } |
| } |
| |
| if (currentBlock.canKillBetweenThisAndDominator(location)) { |
| break; |
| } |
| lastBlock = currentBlock; |
| } |
| |
| if (lastBlock.getBeginNode() instanceof KillingBeginNode) { |
| LocationIdentity locationIdentity = ((KillingBeginNode) lastBlock.getBeginNode()).getLocationIdentity(); |
| if ((locationIdentity.isAny() || locationIdentity.equals(location)) && lastBlock != earliestBlock) { |
| // The begin of this block kills the location, so we *have* to schedule the node |
| // in the dominating block. |
| lastBlock = lastBlock.getDominator(); |
| } |
| } |
| |
| return lastBlock; |
| } |
| |
| private static void fillKillSet(LocationSet killed, List<Node> subList) { |
| if (!killed.isAny()) { |
| for (Node n : subList) { |
| // Check if this node kills a node in the watch list. |
| if (n instanceof MemoryCheckpoint.Single) { |
| LocationIdentity identity = ((MemoryCheckpoint.Single) n).getLocationIdentity(); |
| killed.add(identity); |
| if (killed.isAny()) { |
| return; |
| } |
| } else if (n instanceof MemoryCheckpoint.Multi) { |
| for (LocationIdentity identity : ((MemoryCheckpoint.Multi) n).getLocationIdentities()) { |
| killed.add(identity); |
| if (killed.isAny()) { |
| return; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static void sortNodesLatestWithinBlock(ControlFlowGraph cfg, BlockMap<List<Node>> earliestBlockToNodesMap, BlockMap<List<Node>> latestBlockToNodesMap, NodeMap<Block> currentNodeMap, |
| BlockMap<ArrayList<FloatingReadNode>> watchListMap, NodeBitMap visited) { |
| for (Block b : cfg.getBlocks()) { |
| sortNodesLatestWithinBlock(b, earliestBlockToNodesMap, latestBlockToNodesMap, currentNodeMap, watchListMap, visited); |
| } |
| } |
| |
| private static void sortNodesLatestWithinBlock(Block b, BlockMap<List<Node>> earliestBlockToNodesMap, BlockMap<List<Node>> latestBlockToNodesMap, NodeMap<Block> nodeMap, |
| BlockMap<ArrayList<FloatingReadNode>> watchListMap, NodeBitMap unprocessed) { |
| List<Node> earliestSorting = earliestBlockToNodesMap.get(b); |
| ArrayList<Node> result = new ArrayList<>(earliestSorting.size()); |
| ArrayList<FloatingReadNode> watchList = null; |
| if (watchListMap != null) { |
| watchList = watchListMap.get(b); |
| assert watchList == null || !b.getKillLocations().isEmpty(); |
| } |
| AbstractBeginNode beginNode = b.getBeginNode(); |
| if (beginNode instanceof LoopExitNode) { |
| LoopExitNode loopExitNode = (LoopExitNode) beginNode; |
| for (ProxyNode proxy : loopExitNode.proxies()) { |
| unprocessed.clear(proxy); |
| ValueNode value = proxy.value(); |
| // if multiple proxies reference the same value, schedule the value of a |
| // proxy once |
| if (value != null && nodeMap.get(value) == b && unprocessed.isMarked(value)) { |
| sortIntoList(value, b, result, nodeMap, unprocessed, null); |
| } |
| } |
| } |
| FixedNode endNode = b.getEndNode(); |
| FixedNode fixedEndNode = null; |
| if (isFixedEnd(endNode)) { |
| // Only if the end node is either a control split or an end node, we need to force |
| // it to be the last node in the schedule. |
| fixedEndNode = endNode; |
| } |
| for (Node n : earliestSorting) { |
| if (n != fixedEndNode) { |
| if (n instanceof FixedNode) { |
| assert nodeMap.get(n) == b; |
| checkWatchList(b, nodeMap, unprocessed, result, watchList, n); |
| sortIntoList(n, b, result, nodeMap, unprocessed, null); |
| } else if (nodeMap.get(n) == b && n instanceof FloatingReadNode) { |
| FloatingReadNode floatingReadNode = (FloatingReadNode) n; |
| if (isImplicitNullOpportunity(floatingReadNode, b)) { |
| // Schedule at the beginning of the block. |
| sortIntoList(floatingReadNode, b, result, nodeMap, unprocessed, null); |
| } else { |
| LocationIdentity location = floatingReadNode.getLocationIdentity(); |
| if (b.canKill(location)) { |
| // This read can be killed in this block, add to watch list. |
| if (watchList == null) { |
| watchList = new ArrayList<>(); |
| } |
| watchList.add(floatingReadNode); |
| } |
| } |
| } |
| } |
| } |
| |
| for (Node n : latestBlockToNodesMap.get(b)) { |
| assert nodeMap.get(n) == b : n; |
| assert !(n instanceof FixedNode); |
| if (unprocessed.isMarked(n)) { |
| sortIntoList(n, b, result, nodeMap, unprocessed, fixedEndNode); |
| } |
| } |
| |
| if (endNode != null && unprocessed.isMarked(endNode)) { |
| sortIntoList(endNode, b, result, nodeMap, unprocessed, null); |
| } |
| |
| latestBlockToNodesMap.put(b, result); |
| } |
| |
| private static void checkWatchList(Block b, NodeMap<Block> nodeMap, NodeBitMap unprocessed, ArrayList<Node> result, ArrayList<FloatingReadNode> watchList, Node n) { |
| if (watchList != null && !watchList.isEmpty()) { |
| // Check if this node kills a node in the watch list. |
| if (n instanceof MemoryCheckpoint.Single) { |
| LocationIdentity identity = ((MemoryCheckpoint.Single) n).getLocationIdentity(); |
| checkWatchList(watchList, identity, b, result, nodeMap, unprocessed); |
| } else if (n instanceof MemoryCheckpoint.Multi) { |
| for (LocationIdentity identity : ((MemoryCheckpoint.Multi) n).getLocationIdentities()) { |
| checkWatchList(watchList, identity, b, result, nodeMap, unprocessed); |
| } |
| } |
| } |
| } |
| |
| private static void checkWatchList(ArrayList<FloatingReadNode> watchList, LocationIdentity identity, Block b, ArrayList<Node> result, NodeMap<Block> nodeMap, NodeBitMap unprocessed) { |
| if (identity.isImmutable()) { |
| // Nothing to do. This can happen for an initialization write. |
| } else if (identity.isAny()) { |
| for (FloatingReadNode r : watchList) { |
| if (unprocessed.isMarked(r)) { |
| sortIntoList(r, b, result, nodeMap, unprocessed, null); |
| } |
| } |
| watchList.clear(); |
| } else { |
| int index = 0; |
| while (index < watchList.size()) { |
| FloatingReadNode r = watchList.get(index); |
| LocationIdentity locationIdentity = r.getLocationIdentity(); |
| assert locationIdentity.isMutable(); |
| if (unprocessed.isMarked(r)) { |
| if (identity.overlaps(locationIdentity)) { |
| sortIntoList(r, b, result, nodeMap, unprocessed, null); |
| } else { |
| ++index; |
| continue; |
| } |
| } |
| int lastIndex = watchList.size() - 1; |
| watchList.set(index, watchList.get(lastIndex)); |
| watchList.remove(lastIndex); |
| } |
| } |
| } |
| |
| private static void sortIntoList(Node n, Block b, ArrayList<Node> result, NodeMap<Block> nodeMap, NodeBitMap unprocessed, Node excludeNode) { |
| assert unprocessed.isMarked(n) : n; |
| assert nodeMap.get(n) == b; |
| |
| if (n instanceof PhiNode) { |
| return; |
| } |
| |
| unprocessed.clear(n); |
| |
| for (Node input : n.inputs()) { |
| if (nodeMap.get(input) == b && unprocessed.isMarked(input) && input != excludeNode) { |
| sortIntoList(input, b, result, nodeMap, unprocessed, excludeNode); |
| } |
| } |
| |
| if (n instanceof ProxyNode) { |
| // Skip proxy nodes. |
| } else { |
| result.add(n); |
| } |
| |
| } |
| |
| protected void calcLatestBlock(Block earliestBlock, SchedulingStrategy strategy, Node currentNode, NodeMap<Block> currentNodeMap, LocationIdentity constrainingLocation, |
| BlockMap<ArrayList<FloatingReadNode>> watchListMap, BlockMap<List<Node>> latestBlockToNodesMap, NodeBitMap visited, boolean immutableGraph) { |
| Block latestBlock = null; |
| if (!currentNode.hasUsages()) { |
| assert currentNode instanceof GuardNode; |
| latestBlock = earliestBlock; |
| } else { |
| assert currentNode.hasUsages(); |
| for (Node usage : currentNode.usages()) { |
| if (immutableGraph && !visited.contains(usage)) { |
| /* |
| * Normally, dead nodes are deleted by the scheduler before we reach this |
| * point. Only when the scheduler is asked to not modify a graph, we can see |
| * dead nodes here. |
| */ |
| continue; |
| } |
| latestBlock = calcBlockForUsage(currentNode, usage, latestBlock, currentNodeMap); |
| } |
| |
| assert latestBlock != null : currentNode; |
| |
| if (strategy == SchedulingStrategy.FINAL_SCHEDULE || strategy == SchedulingStrategy.LATEST_OUT_OF_LOOPS) { |
| Block currentBlock = latestBlock; |
| while (currentBlock.getLoopDepth() > earliestBlock.getLoopDepth() && currentBlock != earliestBlock.getDominator()) { |
| Block previousCurrentBlock = currentBlock; |
| currentBlock = currentBlock.getDominator(); |
| if (previousCurrentBlock.isLoopHeader()) { |
| if (currentBlock.probability() < latestBlock.probability() || ((StructuredGraph) currentNode.graph()).hasValueProxies()) { |
| // Only assign new latest block if frequency is actually lower or if |
| // loop proxies would be required otherwise. |
| latestBlock = currentBlock; |
| } |
| } |
| } |
| } |
| |
| if (latestBlock != earliestBlock && latestBlock != earliestBlock.getDominator() && constrainingLocation != null) { |
| latestBlock = checkKillsBetween(earliestBlock, latestBlock, constrainingLocation); |
| } |
| } |
| |
| if (latestBlock != earliestBlock && currentNode instanceof FloatingReadNode) { |
| |
| FloatingReadNode floatingReadNode = (FloatingReadNode) currentNode; |
| if (isImplicitNullOpportunity(floatingReadNode, earliestBlock) && earliestBlock.probability() < latestBlock.probability() * IMPLICIT_NULL_CHECK_OPPORTUNITY_PROBABILITY_FACTOR) { |
| latestBlock = earliestBlock; |
| } |
| } |
| |
| selectLatestBlock(currentNode, earliestBlock, latestBlock, currentNodeMap, watchListMap, constrainingLocation, latestBlockToNodesMap); |
| } |
| |
| private static boolean isImplicitNullOpportunity(FloatingReadNode floatingReadNode, Block block) { |
| |
| Node pred = block.getBeginNode().predecessor(); |
| if (pred instanceof IfNode) { |
| IfNode ifNode = (IfNode) pred; |
| if (ifNode.condition() instanceof IsNullNode) { |
| IsNullNode isNullNode = (IsNullNode) ifNode.condition(); |
| if (getUnproxifiedUncompressed(floatingReadNode.getAddress().getBase()) == getUnproxifiedUncompressed(isNullNode.getValue())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static Node getUnproxifiedUncompressed(Node node) { |
| Node result = node; |
| while (true) { |
| if (result instanceof ValueProxy) { |
| ValueProxy valueProxy = (ValueProxy) result; |
| result = valueProxy.getOriginalNode(); |
| } else if (result instanceof ConvertNode) { |
| ConvertNode convertNode = (ConvertNode) result; |
| if (convertNode.mayNullCheckSkipConversion()) { |
| result = convertNode.getValue(); |
| } else { |
| break; |
| } |
| } else { |
| break; |
| } |
| } |
| return result; |
| } |
| |
| private static Block calcBlockForUsage(Node node, Node usage, Block startBlock, NodeMap<Block> currentNodeMap) { |
| assert !(node instanceof PhiNode); |
| Block currentBlock = startBlock; |
| if (usage instanceof PhiNode) { |
| // An input to a PhiNode is used at the end of the predecessor block that |
| // corresponds to the PhiNode input. One PhiNode can use an input multiple times. |
| PhiNode phi = (PhiNode) usage; |
| AbstractMergeNode merge = phi.merge(); |
| Block mergeBlock = currentNodeMap.get(merge); |
| for (int i = 0; i < phi.valueCount(); ++i) { |
| if (phi.valueAt(i) == node) { |
| Block otherBlock = mergeBlock.getPredecessors()[i]; |
| currentBlock = AbstractControlFlowGraph.commonDominatorTyped(currentBlock, otherBlock); |
| } |
| } |
| } else if (usage instanceof AbstractBeginNode) { |
| AbstractBeginNode abstractBeginNode = (AbstractBeginNode) usage; |
| if (abstractBeginNode instanceof StartNode) { |
| currentBlock = AbstractControlFlowGraph.commonDominatorTyped(currentBlock, currentNodeMap.get(abstractBeginNode)); |
| } else { |
| Block otherBlock = currentNodeMap.get(abstractBeginNode).getDominator(); |
| currentBlock = AbstractControlFlowGraph.commonDominatorTyped(currentBlock, otherBlock); |
| } |
| } else { |
| // All other types of usages: Put the input into the same block as the usage. |
| Block otherBlock = currentNodeMap.get(usage); |
| if (usage instanceof ProxyNode) { |
| ProxyNode proxyNode = (ProxyNode) usage; |
| otherBlock = currentNodeMap.get(proxyNode.proxyPoint()); |
| |
| } |
| currentBlock = AbstractControlFlowGraph.commonDominatorTyped(currentBlock, otherBlock); |
| } |
| return currentBlock; |
| } |
| |
| /** |
| * Micro block that is allocated for each fixed node and captures all floating nodes that |
| * need to be scheduled immediately after the corresponding fixed node. |
| */ |
| private static class MicroBlock { |
| private final int id; |
| private int nodeCount; |
| private NodeEntry head; |
| private NodeEntry tail; |
| |
| MicroBlock(int id) { |
| this.id = id; |
| } |
| |
| /** |
| * Adds a new floating node into the micro block. |
| */ |
| public void add(Node node) { |
| assert !(node instanceof FixedNode) : node; |
| NodeEntry newTail = new NodeEntry(node); |
| if (tail == null) { |
| tail = head = newTail; |
| } else { |
| tail.next = newTail; |
| tail = newTail; |
| } |
| nodeCount++; |
| } |
| |
| /** |
| * Number of nodes in this micro block. |
| */ |
| public int getNodeCount() { |
| assert getActualNodeCount() == nodeCount : getActualNodeCount() + " != " + nodeCount; |
| return nodeCount; |
| } |
| |
| private int getActualNodeCount() { |
| int count = 0; |
| for (NodeEntry e = head; e != null; e = e.next) { |
| count++; |
| } |
| return count; |
| } |
| |
| /** |
| * The id of the micro block, with a block always associated with a lower id than its |
| * successors. |
| */ |
| public int getId() { |
| return id; |
| } |
| |
| /** |
| * First node of the linked list of nodes of this micro block. |
| */ |
| public NodeEntry getFirstNode() { |
| return head; |
| } |
| |
| /** |
| * Takes all nodes in this micro blocks and prepends them to the nodes of the given |
| * parameter. |
| * |
| * @param newBlock the new block for the nodes |
| */ |
| public void prependChildrenTo(MicroBlock newBlock) { |
| if (tail != null) { |
| assert head != null; |
| tail.next = newBlock.head; |
| newBlock.head = head; |
| head = tail = null; |
| newBlock.nodeCount += nodeCount; |
| nodeCount = 0; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("MicroBlock[id=%d]", id); |
| } |
| |
| @Override |
| public int hashCode() { |
| return id; |
| } |
| } |
| |
| /** |
| * Entry in the linked list of nodes. |
| */ |
| private static class NodeEntry { |
| private final Node node; |
| private NodeEntry next; |
| |
| NodeEntry(Node node) { |
| this.node = node; |
| this.next = null; |
| } |
| |
| public NodeEntry getNext() { |
| return next; |
| } |
| |
| public Node getNode() { |
| return node; |
| } |
| } |
| |
| private void scheduleEarliestIterative(BlockMap<List<Node>> blockToNodes, NodeMap<Block> nodeToBlock, NodeBitMap visited, StructuredGraph graph, boolean immutableGraph, |
| boolean withGuardOrder) { |
| |
| NodeMap<MicroBlock> entries = graph.createNodeMap(); |
| NodeStack stack = new NodeStack(); |
| |
| // Initialize with fixed nodes. |
| MicroBlock startBlock = null; |
| int nextId = 1; |
| for (Block b : cfg.reversePostOrder()) { |
| for (FixedNode current : b.getBeginNode().getBlockNodes()) { |
| MicroBlock microBlock = new MicroBlock(nextId++); |
| entries.set(current, microBlock); |
| boolean isNew = visited.checkAndMarkInc(current); |
| assert isNew; |
| if (startBlock == null) { |
| startBlock = microBlock; |
| } |
| } |
| } |
| |
| if (graph.getGuardsStage().allowsFloatingGuards() && graph.getNodes(GuardNode.TYPE).isNotEmpty()) { |
| // Now process guards. |
| if (GuardPriorities.getValue(graph.getOptions()) && withGuardOrder) { |
| EnumMap<GuardPriority, List<GuardNode>> guardsByPriority = new EnumMap<>(GuardPriority.class); |
| for (GuardNode guard : graph.getNodes(GuardNode.TYPE)) { |
| guardsByPriority.computeIfAbsent(guard.computePriority(), p -> new ArrayList<>()).add(guard); |
| } |
| // `EnumMap.values` returns values in "natural" key order |
| for (List<GuardNode> guards : guardsByPriority.values()) { |
| processNodes(visited, entries, stack, startBlock, guards); |
| } |
| GuardOrder.resortGuards(graph, entries, stack); |
| } else { |
| processNodes(visited, entries, stack, startBlock, graph.getNodes(GuardNode.TYPE)); |
| } |
| } else { |
| assert graph.getNodes(GuardNode.TYPE).isEmpty(); |
| } |
| |
| // Now process inputs of fixed nodes. |
| for (Block b : cfg.reversePostOrder()) { |
| for (FixedNode current : b.getBeginNode().getBlockNodes()) { |
| processNodes(visited, entries, stack, startBlock, current.inputs()); |
| } |
| } |
| |
| if (visited.getCounter() < graph.getNodeCount()) { |
| // Visit back input edges of loop phis. |
| boolean changed; |
| boolean unmarkedPhi; |
| do { |
| changed = false; |
| unmarkedPhi = false; |
| for (LoopBeginNode loopBegin : graph.getNodes(LoopBeginNode.TYPE)) { |
| for (PhiNode phi : loopBegin.phis()) { |
| if (visited.isMarked(phi)) { |
| for (int i = 0; i < loopBegin.getLoopEndCount(); ++i) { |
| Node node = phi.valueAt(i + loopBegin.forwardEndCount()); |
| if (node != null && entries.get(node) == null) { |
| changed = true; |
| processStack(node, startBlock, entries, visited, stack); |
| } |
| } |
| } else { |
| unmarkedPhi = true; |
| } |
| } |
| } |
| |
| /* |
| * the processing of one loop phi could have marked a previously checked loop |
| * phi, therefore this needs to be iterative. |
| */ |
| } while (unmarkedPhi && changed); |
| } |
| |
| // Check for dead nodes. |
| if (!immutableGraph && visited.getCounter() < graph.getNodeCount()) { |
| for (Node n : graph.getNodes()) { |
| if (!visited.isMarked(n)) { |
| n.clearInputs(); |
| n.markDeleted(); |
| } |
| } |
| } |
| |
| for (Block b : cfg.reversePostOrder()) { |
| FixedNode fixedNode = b.getEndNode(); |
| if (fixedNode instanceof ControlSplitNode) { |
| ControlSplitNode controlSplitNode = (ControlSplitNode) fixedNode; |
| MicroBlock endBlock = entries.get(fixedNode); |
| AbstractBeginNode primarySuccessor = controlSplitNode.getPrimarySuccessor(); |
| if (primarySuccessor != null) { |
| endBlock.prependChildrenTo(entries.get(primarySuccessor)); |
| } else { |
| assert endBlock.tail == null; |
| } |
| } |
| } |
| |
| // Create lists for each block |
| for (Block b : cfg.reversePostOrder()) { |
| // Count nodes in block |
| int totalCount = 0; |
| for (FixedNode current : b.getBeginNode().getBlockNodes()) { |
| MicroBlock microBlock = entries.get(current); |
| totalCount += microBlock.getNodeCount() + 1; |
| } |
| |
| // Initialize with begin node, it is always the first node. |
| ArrayList<Node> nodes = new ArrayList<>(totalCount); |
| blockToNodes.put(b, nodes); |
| |
| for (FixedNode current : b.getBeginNode().getBlockNodes()) { |
| MicroBlock microBlock = entries.get(current); |
| nodeToBlock.set(current, b); |
| nodes.add(current); |
| NodeEntry next = microBlock.getFirstNode(); |
| while (next != null) { |
| Node nextNode = next.getNode(); |
| nodeToBlock.set(nextNode, b); |
| nodes.add(nextNode); |
| next = next.getNext(); |
| } |
| } |
| } |
| |
| assert (!Assertions.detailedAssertionsEnabled(cfg.graph.getOptions())) || MemoryScheduleVerification.check(cfg.getStartBlock(), blockToNodes); |
| } |
| |
| private static void processNodes(NodeBitMap visited, NodeMap<MicroBlock> entries, NodeStack stack, MicroBlock startBlock, Iterable<? extends Node> nodes) { |
| for (Node node : nodes) { |
| if (entries.get(node) == null) { |
| processStack(node, startBlock, entries, visited, stack); |
| } |
| } |
| } |
| |
| private static void processStackPhi(NodeStack stack, PhiNode phiNode, NodeMap<MicroBlock> nodeToBlock, NodeBitMap visited) { |
| stack.pop(); |
| if (visited.checkAndMarkInc(phiNode)) { |
| MicroBlock mergeBlock = nodeToBlock.get(phiNode.merge()); |
| assert mergeBlock != null : phiNode; |
| nodeToBlock.set(phiNode, mergeBlock); |
| AbstractMergeNode merge = phiNode.merge(); |
| for (int i = 0; i < merge.forwardEndCount(); ++i) { |
| Node input = phiNode.valueAt(i); |
| if (input != null && nodeToBlock.get(input) == null) { |
| stack.push(input); |
| } |
| } |
| } |
| } |
| |
| private static void processStackProxy(NodeStack stack, ProxyNode proxyNode, NodeMap<MicroBlock> nodeToBlock, NodeBitMap visited) { |
| stack.pop(); |
| if (visited.checkAndMarkInc(proxyNode)) { |
| nodeToBlock.set(proxyNode, nodeToBlock.get(proxyNode.proxyPoint())); |
| Node input = proxyNode.value(); |
| if (input != null && nodeToBlock.get(input) == null) { |
| stack.push(input); |
| } |
| } |
| } |
| |
| private static void processStack(Node first, MicroBlock startBlock, NodeMap<MicroBlock> nodeToMicroBlock, NodeBitMap visited, NodeStack stack) { |
| assert stack.isEmpty(); |
| assert !visited.isMarked(first); |
| stack.push(first); |
| Node current = first; |
| while (true) { |
| if (current instanceof PhiNode) { |
| processStackPhi(stack, (PhiNode) current, nodeToMicroBlock, visited); |
| } else if (current instanceof ProxyNode) { |
| processStackProxy(stack, (ProxyNode) current, nodeToMicroBlock, visited); |
| } else { |
| MicroBlock currentBlock = nodeToMicroBlock.get(current); |
| if (currentBlock == null) { |
| MicroBlock earliestBlock = processInputs(nodeToMicroBlock, stack, startBlock, current); |
| if (earliestBlock == null) { |
| // We need to delay until inputs are processed. |
| } else { |
| // Can immediately process and pop. |
| stack.pop(); |
| visited.checkAndMarkInc(current); |
| nodeToMicroBlock.set(current, earliestBlock); |
| earliestBlock.add(current); |
| } |
| } else { |
| stack.pop(); |
| } |
| } |
| |
| if (stack.isEmpty()) { |
| break; |
| } |
| current = stack.peek(); |
| } |
| } |
| |
| private static class GuardOrder { |
| /** |
| * After an earliest schedule, this will re-sort guards to honor their |
| * {@linkplain StaticDeoptimizingNode#computePriority() priority}. |
| * |
| * Note that this only changes the order of nodes within {@linkplain MicroBlock |
| * micro-blocks}, nodes will not be moved from one micro-block to another. |
| */ |
| private static void resortGuards(StructuredGraph graph, NodeMap<MicroBlock> entries, NodeStack stack) { |
| assert stack.isEmpty(); |
| EconomicSet<MicroBlock> blocksWithGuards = EconomicSet.create(IDENTITY); |
| for (GuardNode guard : graph.getNodes(GuardNode.TYPE)) { |
| MicroBlock block = entries.get(guard); |
| assert block != null : guard + "should already be scheduled to a micro-block"; |
| blocksWithGuards.add(block); |
| } |
| assert !blocksWithGuards.isEmpty(); |
| NodeMap<GuardPriority> priorities = graph.createNodeMap(); |
| NodeBitMap blockNodes = graph.createNodeBitMap(); |
| for (MicroBlock block : blocksWithGuards) { |
| MicroBlock newBlock = resortGuards(block, stack, blockNodes, priorities); |
| assert stack.isEmpty(); |
| assert blockNodes.isEmpty(); |
| if (newBlock != null) { |
| assert block.getNodeCount() == newBlock.getNodeCount(); |
| block.head = newBlock.head; |
| block.tail = newBlock.tail; |
| } |
| } |
| } |
| |
| /** |
| * This resorts guards within one micro-block. |
| * |
| * {@code stack}, {@code blockNodes} and {@code priorities} are just temporary |
| * data-structures which are allocated once by the callers of this method. They should |
| * be in their "initial"/"empty" state when calling this method and when it returns. |
| */ |
| private static MicroBlock resortGuards(MicroBlock block, NodeStack stack, NodeBitMap blockNodes, NodeMap<GuardPriority> priorities) { |
| if (!propagatePriority(block, stack, priorities, blockNodes)) { |
| return null; |
| } |
| |
| Function<GuardNode, GuardPriority> transitiveGuardPriorityGetter = priorities::get; |
| Comparator<GuardNode> globalGuardPriorityComparator = Comparator.comparing(transitiveGuardPriorityGetter).thenComparing(GuardNode::computePriority).thenComparingInt(Node::hashCode); |
| |
| SortedSet<GuardNode> availableGuards = new TreeSet<>(globalGuardPriorityComparator); |
| MicroBlock newBlock = new MicroBlock(block.getId()); |
| |
| NodeBitMap sorted = blockNodes; |
| sorted.invert(); |
| |
| for (NodeEntry e = block.head; e != null; e = e.next) { |
| checkIfAvailable(e.node, stack, sorted, newBlock, availableGuards, false); |
| } |
| do { |
| while (!stack.isEmpty()) { |
| checkIfAvailable(stack.pop(), stack, sorted, newBlock, availableGuards, true); |
| } |
| Iterator<GuardNode> iterator = availableGuards.iterator(); |
| if (iterator.hasNext()) { |
| addNodeToResort(iterator.next(), stack, sorted, newBlock, true); |
| iterator.remove(); |
| } |
| } while (!stack.isEmpty() || !availableGuards.isEmpty()); |
| |
| blockNodes.clearAll(); |
| return newBlock; |
| } |
| |
| /** |
| * This checks if {@code n} can be scheduled, if it is the case, it schedules it now by |
| * calling {@link #addNodeToResort(Node, NodeStack, NodeBitMap, MicroBlock, boolean)}. |
| */ |
| private static void checkIfAvailable(Node n, NodeStack stack, NodeBitMap sorted, Instance.MicroBlock newBlock, SortedSet<GuardNode> availableGuardNodes, boolean pushUsages) { |
| if (sorted.isMarked(n)) { |
| return; |
| } |
| for (Node in : n.inputs()) { |
| if (!sorted.isMarked(in)) { |
| return; |
| } |
| } |
| if (n instanceof GuardNode) { |
| availableGuardNodes.add((GuardNode) n); |
| } else { |
| addNodeToResort(n, stack, sorted, newBlock, pushUsages); |
| } |
| } |
| |
| /** |
| * Add a node to the re-sorted micro-block. This also pushes nodes that need to be |
| * (re-)examined on the stack. |
| */ |
| private static void addNodeToResort(Node n, NodeStack stack, NodeBitMap sorted, MicroBlock newBlock, boolean pushUsages) { |
| sorted.mark(n); |
| newBlock.add(n); |
| if (pushUsages) { |
| for (Node u : n.usages()) { |
| if (!sorted.isMarked(u)) { |
| stack.push(u); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This fills in a map of transitive priorities ({@code priorities}). It also marks the |
| * nodes from this micro-block in {@code blockNodes}. |
| * |
| * The transitive priority of a guard is the highest of its priority and the priority of |
| * the guards that depend on it (transitively). |
| * |
| * This method returns {@code false} if no re-ordering is necessary in this micro-block. |
| */ |
| private static boolean propagatePriority(MicroBlock block, NodeStack stack, NodeMap<GuardPriority> priorities, NodeBitMap blockNodes) { |
| assert stack.isEmpty(); |
| assert blockNodes.isEmpty(); |
| GuardPriority lowestPriority = GuardPriority.highest(); |
| for (NodeEntry e = block.head; e != null; e = e.next) { |
| blockNodes.mark(e.node); |
| if (e.node instanceof GuardNode) { |
| GuardNode guard = (GuardNode) e.node; |
| GuardPriority priority = guard.computePriority(); |
| if (lowestPriority != null) { |
| if (priority.isLowerPriorityThan(lowestPriority)) { |
| lowestPriority = priority; |
| } else if (priority.isHigherPriorityThan(lowestPriority)) { |
| lowestPriority = null; |
| } |
| } |
| stack.push(guard); |
| priorities.set(guard, priority); |
| } |
| } |
| if (lowestPriority != null) { |
| stack.clear(); |
| blockNodes.clearAll(); |
| return false; |
| } |
| |
| do { |
| Node current = stack.pop(); |
| assert blockNodes.isMarked(current); |
| GuardPriority priority = priorities.get(current); |
| for (Node input : current.inputs()) { |
| if (!blockNodes.isMarked(input)) { |
| continue; |
| } |
| GuardPriority inputPriority = priorities.get(input); |
| if (inputPriority == null || inputPriority.isLowerPriorityThan(priority)) { |
| priorities.set(input, priority); |
| stack.push(input); |
| } |
| } |
| } while (!stack.isEmpty()); |
| return true; |
| } |
| } |
| |
| /** |
| * Processes the inputs of given block. Pushes unprocessed inputs onto the stack. Returns |
| * null if there were still unprocessed inputs, otherwise returns the earliest block given |
| * node can be scheduled in. |
| */ |
| private static MicroBlock processInputs(NodeMap<MicroBlock> nodeToBlock, NodeStack stack, MicroBlock startBlock, Node current) { |
| if (current.getNodeClass().isLeafNode()) { |
| return startBlock; |
| } |
| |
| MicroBlock earliestBlock = startBlock; |
| for (Node input : current.inputs()) { |
| MicroBlock inputBlock = nodeToBlock.get(input); |
| if (inputBlock == null) { |
| earliestBlock = null; |
| stack.push(input); |
| } else if (earliestBlock != null && inputBlock.getId() > earliestBlock.getId()) { |
| earliestBlock = inputBlock; |
| } |
| } |
| return earliestBlock; |
| } |
| |
| private static boolean isFixedEnd(FixedNode endNode) { |
| return endNode instanceof ControlSplitNode || endNode instanceof ControlSinkNode || endNode instanceof AbstractEndNode; |
| } |
| |
| public String printScheduleHelper(String desc) { |
| Formatter buf = new Formatter(); |
| buf.format("=== %s / %s ===%n", getCFG().getStartBlock().getBeginNode().graph(), desc); |
| for (Block b : getCFG().getBlocks()) { |
| buf.format("==== b: %s (loopDepth: %s). ", b, b.getLoopDepth()); |
| buf.format("dom: %s. ", b.getDominator()); |
| buf.format("preds: %s. ", Arrays.toString(b.getPredecessors())); |
| buf.format("succs: %s ====%n", Arrays.toString(b.getSuccessors())); |
| |
| if (blockToNodesMap.get(b) != null) { |
| for (Node n : nodesFor(b)) { |
| printNode(n); |
| } |
| } else { |
| for (Node n : b.getNodes()) { |
| printNode(n); |
| } |
| } |
| } |
| buf.format("%n"); |
| return buf.toString(); |
| } |
| |
| private static void printNode(Node n) { |
| Formatter buf = new Formatter(); |
| buf.format("%s", n); |
| if (n instanceof MemoryCheckpoint.Single) { |
| buf.format(" // kills %s", ((MemoryCheckpoint.Single) n).getLocationIdentity()); |
| } else if (n instanceof MemoryCheckpoint.Multi) { |
| buf.format(" // kills "); |
| for (LocationIdentity locid : ((MemoryCheckpoint.Multi) n).getLocationIdentities()) { |
| buf.format("%s, ", locid); |
| } |
| } else if (n instanceof FloatingReadNode) { |
| FloatingReadNode frn = (FloatingReadNode) n; |
| buf.format(" // from %s", frn.getLocationIdentity()); |
| buf.format(", lastAccess: %s", frn.getLastLocationAccess()); |
| buf.format(", address: %s", frn.getAddress()); |
| } else if (n instanceof GuardNode) { |
| buf.format(", anchor: %s", ((GuardNode) n).getAnchor()); |
| } |
| n.getDebug().log("%s", buf); |
| } |
| |
| public ControlFlowGraph getCFG() { |
| return cfg; |
| } |
| |
| /** |
| * Gets the nodes in a given block. |
| */ |
| public List<Node> nodesFor(Block block) { |
| return blockToNodesMap.get(block); |
| } |
| } |
| |
| } |