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 */ 23 package org.graalvm.compiler.virtual.phases.ea; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 28 import org.graalvm.compiler.core.common.cfg.BlockMap; 29 import org.graalvm.compiler.core.common.cfg.Loop; 30 import org.graalvm.compiler.core.common.type.Stamp; 31 import org.graalvm.compiler.debug.DebugContext; 32 import org.graalvm.compiler.debug.GraalError; 33 import org.graalvm.compiler.debug.Indent; 34 import org.graalvm.compiler.graph.Node; 35 import org.graalvm.compiler.graph.NodeBitMap; 36 import org.graalvm.compiler.graph.NodeMap; 37 import org.graalvm.compiler.graph.iterators.NodeIterable; 38 import org.graalvm.compiler.nodes.AbstractMergeNode; 39 import org.graalvm.compiler.nodes.FixedWithNextNode; 40 import org.graalvm.compiler.nodes.IfNode; 41 import org.graalvm.compiler.nodes.LogicConstantNode; 42 import org.graalvm.compiler.nodes.LogicNode; 43 import org.graalvm.compiler.nodes.LoopBeginNode; 44 import org.graalvm.compiler.nodes.LoopExitNode; 45 import org.graalvm.compiler.nodes.PhiNode; 46 import org.graalvm.compiler.nodes.ProxyNode; 47 import org.graalvm.compiler.nodes.StructuredGraph; 48 import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult; 49 import org.graalvm.compiler.nodes.ValueNode; 50 import org.graalvm.compiler.nodes.ValuePhiNode; 51 import org.graalvm.compiler.nodes.cfg.Block; 52 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph; 53 import org.graalvm.compiler.nodes.extended.BoxNode; 54 import org.graalvm.compiler.nodes.util.GraphUtil; 55 import org.graalvm.compiler.nodes.virtual.AllocatedObjectNode; 56 import org.graalvm.compiler.nodes.virtual.CommitAllocationNode; 57 import org.graalvm.compiler.nodes.virtual.VirtualObjectNode; 58 import org.graalvm.compiler.options.OptionValues; 59 import org.graalvm.compiler.phases.graph.ReentrantBlockIterator; 60 import org.graalvm.compiler.phases.graph.ReentrantBlockIterator.BlockIteratorClosure; 61 import org.graalvm.compiler.phases.graph.ReentrantBlockIterator.LoopInfo; 62 import org.graalvm.util.EconomicMap; 63 import org.graalvm.util.EconomicSet; 64 import org.graalvm.util.Equivalence; 65 import org.graalvm.word.LocationIdentity; 66 67 public 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 protected final DebugContext debug; 116 117 public EffectsClosure(ScheduleResult schedule, ControlFlowGraph cfg) { 118 this.schedule = schedule; 119 this.cfg = cfg; 120 this.aliases = cfg.graph.createNodeMap(); 121 this.hasScalarReplacedInputs = cfg.graph.createNodeBitMap(); 122 this.blockEffects = new BlockMap<>(cfg); 123 this.debug = cfg.graph.getDebug(); 124 for (Block block : cfg.getBlocks()) { 125 blockEffects.put(block, new GraphEffectList(debug)); 126 } 127 } 128 129 @Override 130 public boolean hasChanged() { 131 return changed; 132 } 133 134 @Override 135 public boolean needsApplyEffects() { 136 return true; 137 } 138 139 @Override 140 public void applyEffects() { 141 final StructuredGraph graph = cfg.graph; 142 final ArrayList<Node> obsoleteNodes = new ArrayList<>(0); 143 final ArrayList<GraphEffectList> effectList = new ArrayList<>(); 144 /* 145 * Effects are applied during a ordered iteration over the blocks to apply them in the 146 * correct order, e.g., apply the effect that adds a node to the graph before the node is 147 * used. 148 */ 149 BlockIteratorClosure<Void> closure = new BlockIteratorClosure<Void>() { 150 151 @Override 152 protected Void getInitialState() { 153 return null; 154 } 155 156 private void apply(GraphEffectList effects) { 157 if (effects != null && !effects.isEmpty()) { 158 effectList.add(effects); 159 } 160 } 161 162 @Override 163 protected Void processBlock(Block block, Void currentState) { 164 apply(blockEffects.get(block)); 165 return currentState; 166 } 167 168 @Override 169 protected Void merge(Block merge, List<Void> states) { 170 return null; 171 } 172 173 @Override 174 protected Void cloneState(Void oldState) { 175 return oldState; 176 } 177 178 @Override 179 protected List<Void> processLoop(Loop<Block> loop, Void initialState) { 180 LoopInfo<Void> info = ReentrantBlockIterator.processLoop(this, loop, initialState); 181 apply(loopMergeEffects.get(loop)); 182 return info.exitStates; 183 } 184 }; 185 ReentrantBlockIterator.apply(closure, cfg.getStartBlock()); 186 for (GraphEffectList effects : effectList) { 187 effects.apply(graph, obsoleteNodes, false); 188 } 189 /* 190 * Effects that modify the cfg (e.g., removing a branch for an if that got a constant 191 * condition) need to be performed after all other effects, because they change phi value 192 * indexes. 193 */ 194 for (GraphEffectList effects : effectList) { 195 effects.apply(graph, obsoleteNodes, true); 196 } 197 debug.dump(DebugContext.DETAILED_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, debug, "\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, debug, ")\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(debug); 445 this.afterMergeEffects = new GraphEffectList(debug); 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 }