1 /* 2 * Copyright (c) 2011, 2016, 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.phases.common; 24 25 import static org.graalvm.compiler.core.common.GraalOptions.OptEliminateGuards; 26 import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_IGNORED; 27 import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_IGNORED; 28 import static org.graalvm.compiler.phases.common.LoweringPhase.ProcessBlockState.ST_ENTER; 29 import static org.graalvm.compiler.phases.common.LoweringPhase.ProcessBlockState.ST_ENTER_ALWAYS_REACHED; 30 import static org.graalvm.compiler.phases.common.LoweringPhase.ProcessBlockState.ST_LEAVE; 31 import static org.graalvm.compiler.phases.common.LoweringPhase.ProcessBlockState.ST_PROCESS; 32 import static org.graalvm.compiler.phases.common.LoweringPhase.ProcessBlockState.ST_PROCESS_ALWAYS_REACHED; 33 34 import java.util.ArrayList; 35 import java.util.Collection; 36 import java.util.Iterator; 37 import java.util.List; 38 39 import org.graalvm.compiler.core.common.spi.ConstantFieldProvider; 40 import org.graalvm.compiler.core.common.type.StampFactory; 41 import org.graalvm.compiler.debug.DebugCloseable; 42 import org.graalvm.compiler.debug.GraalError; 43 import org.graalvm.compiler.graph.Graph.Mark; 44 import org.graalvm.compiler.graph.Node; 45 import org.graalvm.compiler.graph.NodeBitMap; 46 import org.graalvm.compiler.graph.NodeClass; 47 import org.graalvm.compiler.graph.iterators.NodeIterable; 48 import org.graalvm.compiler.nodeinfo.InputType; 49 import org.graalvm.compiler.nodeinfo.NodeInfo; 50 import org.graalvm.compiler.nodes.AbstractBeginNode; 51 import org.graalvm.compiler.nodes.BeginNode; 52 import org.graalvm.compiler.nodes.FixedGuardNode; 53 import org.graalvm.compiler.nodes.FixedNode; 54 import org.graalvm.compiler.nodes.FixedWithNextNode; 55 import org.graalvm.compiler.nodes.GuardNode; 56 import org.graalvm.compiler.nodes.LogicNode; 57 import org.graalvm.compiler.nodes.StructuredGraph; 58 import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult; 59 import org.graalvm.compiler.nodes.ValueNode; 60 import org.graalvm.compiler.nodes.calc.FloatingNode; 61 import org.graalvm.compiler.nodes.cfg.Block; 62 import org.graalvm.compiler.nodes.extended.AnchoringNode; 63 import org.graalvm.compiler.nodes.extended.GuardedNode; 64 import org.graalvm.compiler.nodes.extended.GuardingNode; 65 import org.graalvm.compiler.nodes.spi.Lowerable; 66 import org.graalvm.compiler.nodes.spi.LoweringProvider; 67 import org.graalvm.compiler.nodes.spi.LoweringTool; 68 import org.graalvm.compiler.nodes.spi.NodeCostProvider; 69 import org.graalvm.compiler.nodes.spi.Replacements; 70 import org.graalvm.compiler.nodes.spi.StampProvider; 71 import org.graalvm.compiler.phases.BasePhase; 72 import org.graalvm.compiler.phases.Phase; 73 import org.graalvm.compiler.phases.schedule.SchedulePhase; 74 import org.graalvm.compiler.phases.tiers.PhaseContext; 75 76 import jdk.vm.ci.meta.ConstantReflectionProvider; 77 import jdk.vm.ci.meta.DeoptimizationAction; 78 import jdk.vm.ci.meta.DeoptimizationReason; 79 import jdk.vm.ci.meta.JavaConstant; 80 import jdk.vm.ci.meta.MetaAccessProvider; 81 82 /** 83 * Processes all {@link Lowerable} nodes to do their lowering. 84 */ 85 public class LoweringPhase extends BasePhase<PhaseContext> { 86 87 @NodeInfo(cycles = CYCLES_IGNORED, size = SIZE_IGNORED) 88 static final class DummyGuardHandle extends ValueNode implements GuardedNode { 89 public static final NodeClass<DummyGuardHandle> TYPE = NodeClass.create(DummyGuardHandle.class); 90 @Input(InputType.Guard) GuardingNode guard; 91 92 protected DummyGuardHandle(GuardingNode guard) { 93 super(TYPE, StampFactory.forVoid()); 94 this.guard = guard; 95 } 96 97 @Override 98 public GuardingNode getGuard() { 99 return guard; 100 } 101 102 @Override 103 public void setGuard(GuardingNode guard) { 104 updateUsagesInterface(this.guard, guard); 105 this.guard = guard; 106 } 107 108 @Override 109 public ValueNode asNode() { 110 return this; 111 } 112 } 113 114 @Override 115 public boolean checkContract() { 116 return false; 117 } 118 119 final class LoweringToolImpl implements LoweringTool { 120 121 private final PhaseContext context; 122 private final NodeBitMap activeGuards; 123 private AnchoringNode guardAnchor; 124 private FixedWithNextNode lastFixedNode; 125 126 LoweringToolImpl(PhaseContext context, AnchoringNode guardAnchor, NodeBitMap activeGuards, FixedWithNextNode lastFixedNode) { 127 this.context = context; 128 this.guardAnchor = guardAnchor; 129 this.activeGuards = activeGuards; 130 this.lastFixedNode = lastFixedNode; 131 } 132 133 @Override 134 public LoweringStage getLoweringStage() { 135 return loweringStage; 136 } 137 138 @Override 139 public ConstantReflectionProvider getConstantReflection() { 140 return context.getConstantReflection(); 141 } 142 143 @Override 144 public ConstantFieldProvider getConstantFieldProvider() { 145 return context.getConstantFieldProvider(); 146 } 147 148 @Override 149 public MetaAccessProvider getMetaAccess() { 150 return context.getMetaAccess(); 151 } 152 153 @Override 154 public LoweringProvider getLowerer() { 155 return context.getLowerer(); 156 } 157 158 @Override 159 public Replacements getReplacements() { 160 return context.getReplacements(); 161 } 162 163 @Override 164 public AnchoringNode getCurrentGuardAnchor() { 165 return guardAnchor; 166 } 167 168 @Override 169 public GuardingNode createGuard(FixedNode before, LogicNode condition, DeoptimizationReason deoptReason, DeoptimizationAction action) { 170 return createGuard(before, condition, deoptReason, action, JavaConstant.NULL_POINTER, false); 171 } 172 173 @Override 174 public StampProvider getStampProvider() { 175 return context.getStampProvider(); 176 } 177 178 @Override 179 public GuardingNode createGuard(FixedNode before, LogicNode condition, DeoptimizationReason deoptReason, DeoptimizationAction action, JavaConstant speculation, boolean negated) { 180 if (OptEliminateGuards.getValue()) { 181 for (Node usage : condition.usages()) { 182 if (!activeGuards.isNew(usage) && activeGuards.isMarked(usage) && ((GuardNode) usage).isNegated() == negated) { 183 return (GuardNode) usage; 184 } 185 } 186 } 187 StructuredGraph graph = before.graph(); 188 if (!condition.graph().getGuardsStage().allowsFloatingGuards()) { 189 FixedGuardNode fixedGuard = graph.add(new FixedGuardNode(condition, deoptReason, action, speculation, negated)); 190 graph.addBeforeFixed(before, fixedGuard); 191 DummyGuardHandle handle = graph.add(new DummyGuardHandle(fixedGuard)); 192 fixedGuard.lower(this); 193 GuardingNode result = handle.getGuard(); 194 handle.safeDelete(); 195 return result; 196 } else { 197 GuardNode newGuard = graph.unique(new GuardNode(condition, guardAnchor, deoptReason, action, negated, speculation)); 198 if (OptEliminateGuards.getValue()) { 199 activeGuards.markAndGrow(newGuard); 200 } 201 return newGuard; 202 } 203 } 204 205 @Override 206 public FixedWithNextNode lastFixedNode() { 207 return lastFixedNode; 208 } 209 210 @Override 211 public NodeCostProvider getNodeCostProvider() { 212 return context.getNodeCostProvider(); 213 } 214 215 private void setLastFixedNode(FixedWithNextNode n) { 216 assert n.isAlive() : n; 217 lastFixedNode = n; 218 } 219 } 220 221 private final CanonicalizerPhase canonicalizer; 222 private final LoweringTool.LoweringStage loweringStage; 223 224 public LoweringPhase(CanonicalizerPhase canonicalizer, LoweringTool.LoweringStage loweringStage) { 225 this.canonicalizer = canonicalizer; 226 this.loweringStage = loweringStage; 227 } 228 229 /** 230 * Checks that second lowering of a given graph did not introduce any new nodes. 231 * 232 * @param graph a graph that was just {@linkplain #lower lowered} 233 * @throws AssertionError if the check fails 234 */ 235 private boolean checkPostLowering(StructuredGraph graph, PhaseContext context) { 236 Mark expectedMark = graph.getMark(); 237 lower(graph, context, LoweringMode.VERIFY_LOWERING); 238 Mark mark = graph.getMark(); 239 assert mark.equals(expectedMark) : graph + ": a second round in the current lowering phase introduced these new nodes: " + graph.getNewNodes(expectedMark).snapshot(); 240 return true; 241 } 242 243 @Override 244 protected void run(final StructuredGraph graph, PhaseContext context) { 245 lower(graph, context, LoweringMode.LOWERING); 246 assert checkPostLowering(graph, context); 247 } 248 249 private void lower(StructuredGraph graph, PhaseContext context, LoweringMode mode) { 250 IncrementalCanonicalizerPhase<PhaseContext> incrementalCanonicalizer = new IncrementalCanonicalizerPhase<>(canonicalizer); 251 incrementalCanonicalizer.appendPhase(new Round(context, mode)); 252 incrementalCanonicalizer.apply(graph, context); 253 assert graph.verify(); 254 } 255 256 /** 257 * Checks that lowering of a given node did not introduce any new {@link Lowerable} nodes that 258 * could be lowered in the current {@link LoweringPhase}. Such nodes must be recursively lowered 259 * as part of lowering {@code node}. 260 * 261 * @param node a node that was just lowered 262 * @param preLoweringMark the graph mark before {@code node} was lowered 263 * @param unscheduledUsages set of {@code node}'s usages that were unscheduled before it was 264 * lowered 265 * @throws AssertionError if the check fails 266 */ 267 private static boolean checkPostNodeLowering(Node node, LoweringToolImpl loweringTool, Mark preLoweringMark, Collection<Node> unscheduledUsages) { 268 StructuredGraph graph = (StructuredGraph) node.graph(); 269 Mark postLoweringMark = graph.getMark(); 270 NodeIterable<Node> newNodesAfterLowering = graph.getNewNodes(preLoweringMark); 271 if (node instanceof FloatingNode) { 272 if (!unscheduledUsages.isEmpty()) { 273 for (Node n : newNodesAfterLowering) { 274 assert !(n instanceof FixedNode) : node.graph() + ": cannot lower floatable node " + node + " as it introduces fixed node(s) but has the following unscheduled usages: " + 275 unscheduledUsages; 276 } 277 } 278 } 279 for (Node n : newNodesAfterLowering) { 280 if (n instanceof Lowerable) { 281 ((Lowerable) n).lower(loweringTool); 282 Mark mark = graph.getMark(); 283 assert postLoweringMark.equals(mark) : graph + ": lowering of " + node + " produced lowerable " + n + " that should have been recursively lowered as it introduces these new nodes: " + 284 graph.getNewNodes(postLoweringMark).snapshot(); 285 } 286 } 287 return true; 288 } 289 290 private enum LoweringMode { 291 LOWERING, 292 VERIFY_LOWERING 293 } 294 295 private final class Round extends Phase { 296 297 private final PhaseContext context; 298 private final LoweringMode mode; 299 private ScheduleResult schedule; 300 private final SchedulePhase schedulePhase; 301 302 private Round(PhaseContext context, LoweringMode mode) { 303 this.context = context; 304 this.mode = mode; 305 306 /* 307 * In VERIFY_LOWERING, we want to verify whether the lowering itself changes the graph. 308 * Make sure we're not detecting spurious changes because the SchedulePhase modifies the 309 * graph. 310 */ 311 boolean immutableSchedule = mode == LoweringMode.VERIFY_LOWERING; 312 313 this.schedulePhase = new SchedulePhase(immutableSchedule); 314 } 315 316 @Override 317 protected CharSequence getName() { 318 switch (mode) { 319 case LOWERING: 320 return "LoweringRound"; 321 case VERIFY_LOWERING: 322 return "VerifyLoweringRound"; 323 default: 324 throw GraalError.shouldNotReachHere(); 325 } 326 } 327 328 @Override 329 public boolean checkContract() { 330 /* 331 * lowering with snippets cannot be fully built in the node costs of all high level 332 * nodes 333 */ 334 return false; 335 } 336 337 @Override 338 public void run(StructuredGraph graph) { 339 schedulePhase.apply(graph, false); 340 schedule = graph.getLastSchedule(); 341 schedule.getCFG().computePostdominators(); 342 Block startBlock = schedule.getCFG().getStartBlock(); 343 ProcessFrame rootFrame = new ProcessFrame(startBlock, graph.createNodeBitMap(), startBlock.getBeginNode(), null); 344 LoweringPhase.processBlock(rootFrame); 345 } 346 347 private class ProcessFrame extends Frame<ProcessFrame> { 348 private final NodeBitMap activeGuards; 349 private AnchoringNode anchor; 350 351 ProcessFrame(Block block, NodeBitMap activeGuards, AnchoringNode anchor, ProcessFrame parent) { 352 super(block, parent); 353 this.activeGuards = activeGuards; 354 this.anchor = anchor; 355 } 356 357 @Override 358 public void preprocess() { 359 this.anchor = Round.this.process(block, activeGuards, anchor); 360 } 361 362 @Override 363 public ProcessFrame enter(Block b) { 364 return new ProcessFrame(b, activeGuards, b.getBeginNode(), this); 365 } 366 367 @Override 368 public Frame<?> enterAlwaysReached(Block b) { 369 AnchoringNode newAnchor = anchor; 370 if (parent != null && b.getLoop() != parent.block.getLoop() && !b.isLoopHeader()) { 371 // We are exiting a loop => cannot reuse the anchor without inserting loop 372 // proxies. 373 newAnchor = b.getBeginNode(); 374 } 375 return new ProcessFrame(b, activeGuards, newAnchor, this); 376 } 377 378 @Override 379 public void postprocess() { 380 if (anchor != null && OptEliminateGuards.getValue()) { 381 for (GuardNode guard : anchor.asNode().usages().filter(GuardNode.class)) { 382 if (activeGuards.isMarkedAndGrow(guard)) { 383 activeGuards.clear(guard); 384 } 385 } 386 } 387 } 388 389 } 390 391 @SuppressWarnings("try") 392 private AnchoringNode process(final Block b, final NodeBitMap activeGuards, final AnchoringNode startAnchor) { 393 394 final LoweringToolImpl loweringTool = new LoweringToolImpl(context, startAnchor, activeGuards, b.getBeginNode()); 395 396 // Lower the instructions of this block. 397 List<Node> nodes = schedule.nodesFor(b); 398 for (Node node : nodes) { 399 400 if (node.isDeleted()) { 401 // This case can happen when previous lowerings deleted nodes. 402 continue; 403 } 404 405 // Cache the next node to be able to reconstruct the previous of the next node 406 // after lowering. 407 FixedNode nextNode = null; 408 if (node instanceof FixedWithNextNode) { 409 nextNode = ((FixedWithNextNode) node).next(); 410 } else { 411 nextNode = loweringTool.lastFixedNode().next(); 412 } 413 414 if (node instanceof Lowerable) { 415 Collection<Node> unscheduledUsages = null; 416 assert (unscheduledUsages = getUnscheduledUsages(node)) != null; 417 Mark preLoweringMark = node.graph().getMark(); 418 try (DebugCloseable s = node.graph().withNodeSourcePosition(node)) { 419 ((Lowerable) node).lower(loweringTool); 420 } 421 if (loweringTool.guardAnchor.asNode().isDeleted()) { 422 // TODO nextNode could be deleted but this is not currently supported 423 assert nextNode.isAlive(); 424 loweringTool.guardAnchor = AbstractBeginNode.prevBegin(nextNode); 425 } 426 assert checkPostNodeLowering(node, loweringTool, preLoweringMark, unscheduledUsages); 427 } 428 429 if (!nextNode.isAlive()) { 430 // can happen when the rest of the block is killed by lowering 431 // (e.g. by an unconditional deopt) 432 break; 433 } else { 434 Node nextLastFixed = nextNode.predecessor(); 435 if (!(nextLastFixed instanceof FixedWithNextNode)) { 436 // insert begin node, to have a valid last fixed for next lowerable node. 437 // This is about lowering a FixedWithNextNode to a control split while this 438 // FixedWithNextNode is followed by some kind of BeginNode. 439 // For example the when a FixedGuard followed by a loop exit is lowered to a 440 // control-split + deopt. 441 AbstractBeginNode begin = node.graph().add(new BeginNode()); 442 nextLastFixed.replaceFirstSuccessor(nextNode, begin); 443 begin.setNext(nextNode); 444 nextLastFixed = begin; 445 } 446 loweringTool.setLastFixedNode((FixedWithNextNode) nextLastFixed); 447 } 448 } 449 return loweringTool.getCurrentGuardAnchor(); 450 } 451 452 /** 453 * Gets all usages of a floating, lowerable node that are unscheduled. 454 * <p> 455 * Given that the lowering of such nodes may introduce fixed nodes, they must be lowered in 456 * the context of a usage that dominates all other usages. The fixed nodes resulting from 457 * lowering are attached to the fixed node context of the dominating usage. This ensures the 458 * post-lowering graph still has a valid schedule. 459 * 460 * @param node a {@link Lowerable} node 461 */ 462 private Collection<Node> getUnscheduledUsages(Node node) { 463 List<Node> unscheduledUsages = new ArrayList<>(); 464 if (node instanceof FloatingNode) { 465 for (Node usage : node.usages()) { 466 if (usage instanceof ValueNode) { 467 if (schedule.getCFG().getNodeToBlock().isNew(usage) || schedule.getCFG().blockFor(usage) == null) { 468 unscheduledUsages.add(usage); 469 } 470 } 471 } 472 } 473 return unscheduledUsages; 474 } 475 } 476 477 enum ProcessBlockState { 478 ST_ENTER, 479 ST_PROCESS, 480 ST_ENTER_ALWAYS_REACHED, 481 ST_LEAVE, 482 ST_PROCESS_ALWAYS_REACHED; 483 } 484 485 /** 486 * This state-machine resembles the following recursion: 487 * 488 * <pre> 489 * void processBlock(Block block) { 490 * preprocess(); 491 * // Process always reached block first. 492 * Block alwaysReachedBlock = block.getPostdominator(); 493 * if (alwaysReachedBlock != null && alwaysReachedBlock.getDominator() == block) { 494 * processBlock(alwaysReachedBlock); 495 * } 496 * 497 * // Now go for the other dominators. 498 * for (Block dominated : block.getDominated()) { 499 * if (dominated != alwaysReachedBlock) { 500 * assert dominated.getDominator() == block; 501 * processBlock(dominated); 502 * } 503 * } 504 * postprocess(); 505 * } 506 * </pre> 507 * 508 * This is necessary, as the recursive implementation quickly exceed the stack depth on SPARC. 509 * 510 * @param rootFrame contains the starting block. 511 */ 512 public static void processBlock(final Frame<?> rootFrame) { 513 ProcessBlockState state = ST_PROCESS; 514 Frame<?> f = rootFrame; 515 while (f != null) { 516 ProcessBlockState nextState; 517 if (state == ST_PROCESS || state == ST_PROCESS_ALWAYS_REACHED) { 518 f.preprocess(); 519 nextState = state == ST_PROCESS_ALWAYS_REACHED ? ST_ENTER : ST_ENTER_ALWAYS_REACHED; 520 } else if (state == ST_ENTER_ALWAYS_REACHED) { 521 if (f.alwaysReachedBlock != null && f.alwaysReachedBlock.getDominator() == f.block) { 522 f = f.enterAlwaysReached(f.alwaysReachedBlock); 523 nextState = ST_PROCESS; 524 } else { 525 nextState = ST_ENTER; 526 } 527 } else if (state == ST_ENTER) { 528 if (f.dominated.hasNext()) { 529 Block n = f.dominated.next(); 530 if (n == f.alwaysReachedBlock) { 531 if (f.dominated.hasNext()) { 532 n = f.dominated.next(); 533 } else { 534 n = null; 535 } 536 } 537 if (n == null) { 538 nextState = ST_LEAVE; 539 } else { 540 f = f.enter(n); 541 assert f.block.getDominator() == f.parent.block; 542 nextState = ST_PROCESS; 543 } 544 } else { 545 nextState = ST_LEAVE; 546 } 547 } else if (state == ST_LEAVE) { 548 f.postprocess(); 549 f = f.parent; 550 nextState = ST_ENTER; 551 } else { 552 throw GraalError.shouldNotReachHere(); 553 } 554 state = nextState; 555 } 556 } 557 558 public static void processBlockBounded(final Frame<?> rootFrame) { 559 ProcessBlockState state = ST_PROCESS; 560 Frame<?> f = rootFrame; 561 while (f != null) { 562 ProcessBlockState nextState; 563 if (state == ST_PROCESS || state == ST_PROCESS_ALWAYS_REACHED) { 564 f.preprocess(); 565 nextState = state == ST_PROCESS_ALWAYS_REACHED ? ST_ENTER : ST_ENTER_ALWAYS_REACHED; 566 } else if (state == ST_ENTER_ALWAYS_REACHED) { 567 if (f.alwaysReachedBlock != null && f.alwaysReachedBlock.getDominator() == f.block) { 568 Frame<?> continueRecur = f.enterAlwaysReached(f.alwaysReachedBlock); 569 if (continueRecur == null) { 570 // stop recursion here 571 f.postprocess(); 572 f = f.parent; 573 state = ST_ENTER; 574 continue; 575 } 576 f = continueRecur; 577 nextState = ST_PROCESS; 578 } else { 579 nextState = ST_ENTER; 580 } 581 } else if (state == ST_ENTER) { 582 if (f.dominated.hasNext()) { 583 Block n = f.dominated.next(); 584 if (n == f.alwaysReachedBlock) { 585 if (f.dominated.hasNext()) { 586 n = f.dominated.next(); 587 } else { 588 n = null; 589 } 590 } 591 if (n == null) { 592 nextState = ST_LEAVE; 593 } else { 594 Frame<?> continueRecur = f.enter(n); 595 if (continueRecur == null) { 596 // stop recursion here 597 f.postprocess(); 598 f = f.parent; 599 state = ST_ENTER; 600 continue; 601 } 602 f = continueRecur; 603 nextState = ST_PROCESS; 604 } 605 } else { 606 nextState = ST_LEAVE; 607 } 608 } else if (state == ST_LEAVE) { 609 f.postprocess(); 610 f = f.parent; 611 nextState = ST_ENTER; 612 } else { 613 throw GraalError.shouldNotReachHere(); 614 } 615 state = nextState; 616 } 617 } 618 619 public abstract static class Frame<T extends Frame<?>> { 620 protected final Block block; 621 final T parent; 622 Iterator<Block> dominated; 623 final Block alwaysReachedBlock; 624 625 public Frame(Block block, T parent) { 626 super(); 627 this.block = block; 628 this.alwaysReachedBlock = block.getPostdominator(); 629 this.dominated = block.getDominated().iterator(); 630 this.parent = parent; 631 } 632 633 public Frame<?> enterAlwaysReached(Block b) { 634 return enter(b); 635 } 636 637 public abstract Frame<?> enter(Block b); 638 639 public abstract void preprocess(); 640 641 public abstract void postprocess(); 642 } 643 644 }