1 /* 2 * Copyright (c) 2017, 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 24 25 package org.graalvm.compiler.phases.common; 26 27 import jdk.internal.vm.compiler.collections.EconomicMap; 28 import jdk.internal.vm.compiler.collections.MapCursor; 29 import org.graalvm.compiler.core.common.GraalOptions; 30 import org.graalvm.compiler.core.common.cfg.BlockMap; 31 import org.graalvm.compiler.core.common.type.FloatStamp; 32 import org.graalvm.compiler.core.common.type.Stamp; 33 import org.graalvm.compiler.core.common.type.StampFactory; 34 import org.graalvm.compiler.debug.CounterKey; 35 import org.graalvm.compiler.debug.DebugContext; 36 import org.graalvm.compiler.graph.Node; 37 import org.graalvm.compiler.graph.NodeMap; 38 import org.graalvm.compiler.graph.NodeStack; 39 import org.graalvm.compiler.graph.Position; 40 import org.graalvm.compiler.nodeinfo.InputType; 41 import org.graalvm.compiler.nodes.AbstractBeginNode; 42 import org.graalvm.compiler.nodes.AbstractMergeNode; 43 import org.graalvm.compiler.nodes.BinaryOpLogicNode; 44 import org.graalvm.compiler.nodes.ConstantNode; 45 import org.graalvm.compiler.nodes.EndNode; 46 import org.graalvm.compiler.nodes.IfNode; 47 import org.graalvm.compiler.nodes.LogicNode; 48 import org.graalvm.compiler.nodes.MergeNode; 49 import org.graalvm.compiler.nodes.NodeView; 50 import org.graalvm.compiler.nodes.PhiNode; 51 import org.graalvm.compiler.nodes.PiNode; 52 import org.graalvm.compiler.nodes.StructuredGraph; 53 import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult; 54 import org.graalvm.compiler.nodes.UnaryOpLogicNode; 55 import org.graalvm.compiler.nodes.ValueNode; 56 import org.graalvm.compiler.nodes.ValuePhiNode; 57 import org.graalvm.compiler.nodes.calc.BinaryNode; 58 import org.graalvm.compiler.nodes.calc.ConditionalNode; 59 import org.graalvm.compiler.nodes.calc.UnaryNode; 60 import org.graalvm.compiler.nodes.cfg.Block; 61 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph; 62 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph.RecursiveVisitor; 63 import org.graalvm.compiler.nodes.extended.IntegerSwitchNode; 64 import org.graalvm.compiler.nodes.memory.FixedAccessNode; 65 import org.graalvm.compiler.nodes.memory.FloatingAccessNode; 66 import org.graalvm.compiler.nodes.memory.FloatingReadNode; 67 import org.graalvm.compiler.nodes.memory.MemoryAccess; 68 import org.graalvm.compiler.nodes.memory.MemoryPhiNode; 69 import org.graalvm.compiler.nodes.util.GraphUtil; 70 import org.graalvm.compiler.phases.BasePhase; 71 import org.graalvm.compiler.phases.Phase; 72 import org.graalvm.compiler.phases.graph.ScheduledNodeIterator; 73 import org.graalvm.compiler.phases.schedule.SchedulePhase; 74 import org.graalvm.compiler.phases.schedule.SchedulePhase.SchedulingStrategy; 75 import org.graalvm.compiler.phases.tiers.LowTierContext; 76 import org.graalvm.compiler.phases.tiers.PhaseContext; 77 78 import jdk.vm.ci.meta.Constant; 79 import jdk.vm.ci.meta.MetaAccessProvider; 80 import jdk.vm.ci.meta.TriState; 81 82 /** 83 * This phase lowers {@link FloatingReadNode FloatingReadNodes} into corresponding fixed reads. 84 */ 85 public class FixReadsPhase extends BasePhase<LowTierContext> { 86 87 private static final CounterKey counterStampsRegistered = DebugContext.counter("FixReads_StampsRegistered"); 88 private static final CounterKey counterIfsKilled = DebugContext.counter("FixReads_KilledIfs"); 89 private static final CounterKey counterConditionalsKilled = DebugContext.counter("FixReads_KilledConditionals"); 90 private static final CounterKey counterCanonicalizedSwitches = DebugContext.counter("FixReads_CanonicalizedSwitches"); 91 private static final CounterKey counterConstantReplacements = DebugContext.counter("FixReads_ConstantReplacement"); 92 private static final CounterKey counterConstantInputReplacements = DebugContext.counter("FixReads_ConstantInputReplacement"); 93 private static final CounterKey counterBetterMergedStamps = DebugContext.counter("FixReads_BetterMergedStamp"); 94 95 protected boolean replaceInputsWithConstants; 96 protected Phase schedulePhase; 97 98 @Override 99 public float codeSizeIncrease() { 100 return 2.0f; 101 } 102 103 private static class FixReadsClosure extends ScheduledNodeIterator { 104 105 @Override 106 protected void processNode(Node node) { 107 if (node instanceof AbstractMergeNode) { 108 AbstractMergeNode mergeNode = (AbstractMergeNode) node; 109 for (MemoryPhiNode memoryPhi : mergeNode.memoryPhis().snapshot()) { 110 // Memory phi nodes are no longer necessary at this point. 111 memoryPhi.replaceAtUsages(null); 112 memoryPhi.safeDelete(); 113 } 114 } else if (node instanceof FloatingAccessNode) { 115 FloatingAccessNode floatingAccessNode = (FloatingAccessNode) node; 116 floatingAccessNode.setLastLocationAccess(null); 117 FixedAccessNode fixedAccess = floatingAccessNode.asFixedNode(); 118 replaceCurrent(fixedAccess); 119 } else if (node instanceof PiNode) { 120 PiNode piNode = (PiNode) node; 121 if (piNode.stamp(NodeView.DEFAULT).isCompatible(piNode.getOriginalNode().stamp(NodeView.DEFAULT))) { 122 // Pi nodes are no longer necessary at this point. 123 piNode.replaceAndDelete(piNode.getOriginalNode()); 124 } 125 } else if (node instanceof MemoryAccess) { 126 MemoryAccess memoryAccess = (MemoryAccess) node; 127 memoryAccess.setLastLocationAccess(null); 128 } 129 } 130 131 } 132 133 protected static class RawConditionalEliminationVisitor implements RecursiveVisitor<Integer> { 134 135 protected final NodeMap<StampElement> stampMap; 136 protected final NodeStack undoOperations; 137 private final ScheduleResult schedule; 138 private final StructuredGraph graph; 139 private final MetaAccessProvider metaAccess; 140 private final boolean replaceConstantInputs; 141 private final BlockMap<Integer> blockActionStart; 142 private final EconomicMap<MergeNode, EconomicMap<ValueNode, Stamp>> endMaps; 143 private final DebugContext debug; 144 145 protected RawConditionalEliminationVisitor(StructuredGraph graph, ScheduleResult schedule, MetaAccessProvider metaAccess, boolean replaceInputsWithConstants) { 146 this.graph = graph; 147 this.debug = graph.getDebug(); 148 this.schedule = schedule; 149 this.metaAccess = metaAccess; 150 blockActionStart = new BlockMap<>(schedule.getCFG()); 151 endMaps = EconomicMap.create(); 152 stampMap = graph.createNodeMap(); 153 undoOperations = new NodeStack(); 154 replaceConstantInputs = replaceInputsWithConstants && GraalOptions.ReplaceInputsWithConstantsBasedOnStamps.getValue(graph.getOptions()); 155 } 156 157 protected void replaceInput(Position p, Node oldInput, Node newConstantInput) { 158 p.set(oldInput, newConstantInput); 159 } 160 161 protected int replaceConstantInputs(Node node) { 162 int replacements = 0; 163 // Check if we can replace any of the inputs with a constant. 164 for (Position p : node.inputPositions()) { 165 Node input = p.get(node); 166 if (p.getInputType() == InputType.Value) { 167 if (input instanceof ValueNode) { 168 ValueNode valueNode = (ValueNode) input; 169 if (valueNode instanceof ConstantNode) { 170 // Input already is a constant. 171 } else { 172 Stamp bestStamp = getBestStamp(valueNode); 173 Constant constant = bestStamp.asConstant(); 174 if (constant != null) { 175 if (bestStamp instanceof FloatStamp) { 176 FloatStamp floatStamp = (FloatStamp) bestStamp; 177 if (floatStamp.contains(0.0d)) { 178 // Could also be -0.0d. 179 continue; 180 } 181 } 182 counterConstantInputReplacements.increment(node.getDebug()); 183 ConstantNode stampConstant = ConstantNode.forConstant(bestStamp, constant, metaAccess, graph); 184 assert stampConstant.stamp(NodeView.DEFAULT).isCompatible(valueNode.stamp(NodeView.DEFAULT)); 185 replaceInput(p, node, stampConstant); 186 replacements++; 187 } 188 } 189 } 190 } 191 } 192 return replacements; 193 } 194 195 protected void processNode(Node node) { 196 assert node.isAlive(); 197 198 if (replaceConstantInputs) { 199 replaceConstantInputs(node); 200 } 201 202 if (node instanceof MergeNode) { 203 registerCombinedStamps((MergeNode) node); 204 } 205 206 if (node instanceof AbstractBeginNode) { 207 processAbstractBegin((AbstractBeginNode) node); 208 } else if (node instanceof IfNode) { 209 processIf((IfNode) node); 210 } else if (node instanceof IntegerSwitchNode) { 211 processIntegerSwitch((IntegerSwitchNode) node); 212 } else if (node instanceof BinaryNode) { 213 processBinary((BinaryNode) node); 214 } else if (node instanceof ConditionalNode) { 215 processConditional((ConditionalNode) node); 216 } else if (node instanceof UnaryNode) { 217 processUnary((UnaryNode) node); 218 } else if (node instanceof EndNode) { 219 processEnd((EndNode) node); 220 } 221 } 222 223 protected void registerCombinedStamps(MergeNode node) { 224 EconomicMap<ValueNode, Stamp> endMap = endMaps.get(node); 225 MapCursor<ValueNode, Stamp> entries = endMap.getEntries(); 226 while (entries.advance()) { 227 ValueNode value = entries.getKey(); 228 if (value.isDeleted()) { 229 // nodes from this map can be deleted when a loop dies 230 continue; 231 } 232 if (registerNewValueStamp(value, entries.getValue())) { 233 counterBetterMergedStamps.increment(debug); 234 } 235 } 236 } 237 238 protected void processEnd(EndNode node) { 239 AbstractMergeNode abstractMerge = node.merge(); 240 if (abstractMerge instanceof MergeNode) { 241 MergeNode merge = (MergeNode) abstractMerge; 242 243 NodeMap<Block> blockToNodeMap = this.schedule.getNodeToBlockMap(); 244 Block mergeBlock = blockToNodeMap.get(merge); 245 Block mergeBlockDominator = mergeBlock.getDominator(); 246 Block currentBlock = blockToNodeMap.get(node); 247 248 EconomicMap<ValueNode, Stamp> currentEndMap = endMaps.get(merge); 249 250 if (currentEndMap == null || !currentEndMap.isEmpty()) { 251 252 EconomicMap<ValueNode, Stamp> endMap = EconomicMap.create(); 253 254 // Process phis 255 for (ValuePhiNode phi : merge.valuePhis()) { 256 if (currentEndMap == null || currentEndMap.containsKey(phi)) { 257 ValueNode valueAt = phi.valueAt(node); 258 Stamp bestStamp = getBestStamp(valueAt); 259 260 if (currentEndMap != null) { 261 bestStamp = bestStamp.meet(currentEndMap.get(phi)); 262 } 263 264 if (!bestStamp.equals(phi.stamp(NodeView.DEFAULT))) { 265 endMap.put(phi, bestStamp); 266 } 267 } 268 } 269 270 int lastMark = undoOperations.size(); 271 while (currentBlock != mergeBlockDominator) { 272 int mark = blockActionStart.get(currentBlock); 273 for (int i = lastMark - 1; i >= mark; --i) { 274 ValueNode nodeWithNewStamp = (ValueNode) undoOperations.get(i); 275 276 if (nodeWithNewStamp.isDeleted() || nodeWithNewStamp instanceof LogicNode || nodeWithNewStamp instanceof ConstantNode || blockToNodeMap.isNew(nodeWithNewStamp)) { 277 continue; 278 } 279 280 Block block = getBlock(nodeWithNewStamp, blockToNodeMap); 281 if (block == null || block.getId() <= mergeBlockDominator.getId()) { 282 // Node with new stamp in path to the merge block dominator and that 283 // at the same time was defined at least in the merge block 284 // dominator (i.e., therefore can be used after the merge.) 285 286 Stamp bestStamp = getBestStamp(nodeWithNewStamp); 287 assert bestStamp != null; 288 289 if (currentEndMap != null) { 290 Stamp otherEndsStamp = currentEndMap.get(nodeWithNewStamp); 291 if (otherEndsStamp == null) { 292 // No stamp registered in one of the previously processed 293 // ends => skip. 294 continue; 295 } 296 bestStamp = bestStamp.meet(otherEndsStamp); 297 } 298 299 if (nodeWithNewStamp.stamp(NodeView.DEFAULT).tryImproveWith(bestStamp) == null) { 300 // No point in registering the stamp. 301 } else { 302 endMap.put(nodeWithNewStamp, bestStamp); 303 } 304 } 305 } 306 currentBlock = currentBlock.getDominator(); 307 } 308 309 endMaps.put(merge, endMap); 310 } 311 } 312 } 313 314 private static Block getBlock(ValueNode node, NodeMap<Block> blockToNodeMap) { 315 if (node instanceof PhiNode) { 316 PhiNode phiNode = (PhiNode) node; 317 return blockToNodeMap.get(phiNode.merge()); 318 } 319 return blockToNodeMap.get(node); 320 } 321 322 protected void processUnary(UnaryNode node) { 323 Stamp newStamp = node.foldStamp(getBestStamp(node.getValue())); 324 if (!checkReplaceWithConstant(newStamp, node)) { 325 registerNewValueStamp(node, newStamp); 326 } 327 } 328 329 protected boolean checkReplaceWithConstant(Stamp newStamp, ValueNode node) { 330 Constant constant = newStamp.asConstant(); 331 if (constant != null && !(node instanceof ConstantNode)) { 332 ConstantNode stampConstant = ConstantNode.forConstant(newStamp, constant, metaAccess, graph); 333 debug.log("RawConditionElimination: constant stamp replaces %1s with %1s", node, stampConstant); 334 counterConstantReplacements.increment(debug); 335 node.replaceAtUsages(InputType.Value, stampConstant); 336 GraphUtil.tryKillUnused(node); 337 return true; 338 } 339 return false; 340 } 341 342 protected void processBinary(BinaryNode node) { 343 Stamp xStamp = getBestStamp(node.getX()); 344 Stamp yStamp = getBestStamp(node.getY()); 345 Stamp newStamp = node.foldStamp(xStamp, yStamp); 346 if (!checkReplaceWithConstant(newStamp, node)) { 347 registerNewValueStamp(node, newStamp); 348 } 349 } 350 351 protected void processIntegerSwitch(IntegerSwitchNode node) { 352 Stamp bestStamp = getBestStamp(node.value()); 353 if (node.tryRemoveUnreachableKeys(null, bestStamp)) { 354 debug.log("\t Canonicalized integer switch %s for value %s and stamp %s", node, node.value(), bestStamp); 355 counterCanonicalizedSwitches.increment(debug); 356 } 357 } 358 359 protected void processIf(IfNode node) { 360 TriState result = tryProveCondition(node.condition()); 361 if (result != TriState.UNKNOWN) { 362 boolean isTrue = (result == TriState.TRUE); 363 AbstractBeginNode survivingSuccessor = node.getSuccessor(isTrue); 364 survivingSuccessor.replaceAtUsages(null); 365 survivingSuccessor.replaceAtPredecessor(null); 366 node.replaceAtPredecessor(survivingSuccessor); 367 GraphUtil.killCFG(node); 368 369 counterIfsKilled.increment(debug); 370 } 371 } 372 373 protected void processConditional(ConditionalNode node) { 374 TriState result = tryProveCondition(node.condition()); 375 if (result != TriState.UNKNOWN) { 376 boolean isTrue = (result == TriState.TRUE); 377 counterConditionalsKilled.increment(debug); 378 node.replaceAndDelete(isTrue ? node.trueValue() : node.falseValue()); 379 } else { 380 Stamp trueStamp = getBestStamp(node.trueValue()); 381 Stamp falseStamp = getBestStamp(node.falseValue()); 382 registerNewStamp(node, trueStamp.meet(falseStamp)); 383 } 384 } 385 386 protected TriState tryProveCondition(LogicNode condition) { 387 Stamp conditionStamp = this.getBestStamp(condition); 388 if (conditionStamp == StampFactory.tautology()) { 389 return TriState.TRUE; 390 } else if (conditionStamp == StampFactory.contradiction()) { 391 return TriState.FALSE; 392 } 393 394 if (condition instanceof UnaryOpLogicNode) { 395 UnaryOpLogicNode unaryOpLogicNode = (UnaryOpLogicNode) condition; 396 return unaryOpLogicNode.tryFold(this.getBestStamp(unaryOpLogicNode.getValue())); 397 } else if (condition instanceof BinaryOpLogicNode) { 398 BinaryOpLogicNode binaryOpLogicNode = (BinaryOpLogicNode) condition; 399 return binaryOpLogicNode.tryFold(this.getBestStamp(binaryOpLogicNode.getX()), this.getBestStamp(binaryOpLogicNode.getY())); 400 } 401 402 return TriState.UNKNOWN; 403 } 404 405 protected void processAbstractBegin(AbstractBeginNode beginNode) { 406 Node predecessor = beginNode.predecessor(); 407 if (predecessor instanceof IfNode) { 408 IfNode ifNode = (IfNode) predecessor; 409 boolean negated = (ifNode.falseSuccessor() == beginNode); 410 LogicNode condition = ifNode.condition(); 411 registerNewCondition(condition, negated); 412 } else if (predecessor instanceof IntegerSwitchNode) { 413 IntegerSwitchNode integerSwitchNode = (IntegerSwitchNode) predecessor; 414 registerIntegerSwitch(beginNode, integerSwitchNode); 415 } 416 } 417 418 private void registerIntegerSwitch(AbstractBeginNode beginNode, IntegerSwitchNode integerSwitchNode) { 419 registerNewValueStamp(integerSwitchNode.value(), integerSwitchNode.getValueStampForSuccessor(beginNode)); 420 } 421 422 protected void registerNewCondition(LogicNode condition, boolean negated) { 423 if (condition instanceof UnaryOpLogicNode) { 424 UnaryOpLogicNode unaryLogicNode = (UnaryOpLogicNode) condition; 425 ValueNode value = unaryLogicNode.getValue(); 426 Stamp newStamp = unaryLogicNode.getSucceedingStampForValue(negated); 427 registerNewValueStamp(value, newStamp); 428 } else if (condition instanceof BinaryOpLogicNode) { 429 BinaryOpLogicNode binaryOpLogicNode = (BinaryOpLogicNode) condition; 430 ValueNode x = binaryOpLogicNode.getX(); 431 ValueNode y = binaryOpLogicNode.getY(); 432 Stamp xStamp = getBestStamp(x); 433 Stamp yStamp = getBestStamp(y); 434 registerNewValueStamp(x, binaryOpLogicNode.getSucceedingStampForX(negated, xStamp, yStamp)); 435 registerNewValueStamp(y, binaryOpLogicNode.getSucceedingStampForY(negated, xStamp, yStamp)); 436 } 437 registerCondition(condition, negated); 438 } 439 440 protected void registerCondition(LogicNode condition, boolean negated) { 441 registerNewStamp(condition, negated ? StampFactory.contradiction() : StampFactory.tautology()); 442 } 443 444 protected boolean registerNewValueStamp(ValueNode value, Stamp newStamp) { 445 if (newStamp != null && !value.isConstant()) { 446 Stamp currentStamp = getBestStamp(value); 447 Stamp betterStamp = currentStamp.tryImproveWith(newStamp); 448 if (betterStamp != null) { 449 registerNewStamp(value, betterStamp); 450 return true; 451 } 452 } 453 return false; 454 } 455 456 protected void registerNewStamp(ValueNode value, Stamp newStamp) { 457 counterStampsRegistered.increment(debug); 458 debug.log("\t Saving stamp for node %s stamp %s", value, newStamp); 459 ValueNode originalNode = value; 460 stampMap.setAndGrow(originalNode, new StampElement(newStamp, stampMap.getAndGrow(originalNode))); 461 undoOperations.push(originalNode); 462 } 463 464 protected Stamp getBestStamp(ValueNode value) { 465 ValueNode originalNode = value; 466 StampElement currentStamp = stampMap.getAndGrow(originalNode); 467 if (currentStamp == null) { 468 return value.stamp(NodeView.DEFAULT); 469 } 470 return currentStamp.getStamp(); 471 } 472 473 @Override 474 public Integer enter(Block b) { 475 int mark = undoOperations.size(); 476 blockActionStart.put(b, mark); 477 for (Node n : schedule.getBlockToNodesMap().get(b)) { 478 if (n.isAlive()) { 479 processNode(n); 480 } 481 } 482 return mark; 483 } 484 485 @Override 486 public void exit(Block b, Integer state) { 487 int mark = state; 488 while (undoOperations.size() > mark) { 489 Node node = undoOperations.pop(); 490 if (node.isAlive()) { 491 stampMap.set(node, stampMap.get(node).getParent()); 492 } 493 } 494 } 495 496 } 497 498 public FixReadsPhase(boolean replaceInputsWithConstants, Phase schedulePhase) { 499 this.replaceInputsWithConstants = replaceInputsWithConstants; 500 this.schedulePhase = schedulePhase; 501 } 502 503 @Override 504 protected void run(StructuredGraph graph, LowTierContext context) { 505 schedulePhase.apply(graph); 506 ScheduleResult schedule = graph.getLastSchedule(); 507 FixReadsClosure fixReadsClosure = new FixReadsClosure(); 508 for (Block block : schedule.getCFG().getBlocks()) { 509 fixReadsClosure.processNodes(block, schedule); 510 } 511 if (GraalOptions.RawConditionalElimination.getValue(graph.getOptions())) { 512 schedule.getCFG().visitDominatorTree(createVisitor(graph, schedule, context), false); 513 } 514 graph.setAfterFixReadPhase(true); 515 } 516 517 public static class RawCEPhase extends BasePhase<LowTierContext> { 518 519 private final boolean replaceInputsWithConstants; 520 521 public RawCEPhase(boolean replaceInputsWithConstants) { 522 this.replaceInputsWithConstants = replaceInputsWithConstants; 523 } 524 525 @Override 526 protected CharSequence getName() { 527 return "RawCEPhase"; 528 } 529 530 @Override 531 protected void run(StructuredGraph graph, LowTierContext context) { 532 if (GraalOptions.RawConditionalElimination.getValue(graph.getOptions())) { 533 SchedulePhase schedulePhase = new SchedulePhase(SchedulingStrategy.LATEST, true); 534 schedulePhase.apply(graph); 535 ScheduleResult schedule = graph.getLastSchedule(); 536 schedule.getCFG().visitDominatorTree(new RawConditionalEliminationVisitor(graph, schedule, context.getMetaAccess(), replaceInputsWithConstants), false); 537 } 538 } 539 } 540 541 protected ControlFlowGraph.RecursiveVisitor<?> createVisitor(StructuredGraph graph, ScheduleResult schedule, PhaseContext context) { 542 return new RawConditionalEliminationVisitor(graph, schedule, context.getMetaAccess(), replaceInputsWithConstants); 543 } 544 545 protected static final class StampElement { 546 private final Stamp stamp; 547 private final StampElement parent; 548 549 public StampElement(Stamp stamp, StampElement parent) { 550 this.stamp = stamp; 551 this.parent = parent; 552 } 553 554 public StampElement getParent() { 555 return parent; 556 } 557 558 public Stamp getStamp() { 559 return stamp; 560 } 561 562 @Override 563 public String toString() { 564 StringBuilder result = new StringBuilder(); 565 result.append(stamp); 566 if (this.parent != null) { 567 result.append(" ("); 568 result.append(this.parent.toString()); 569 result.append(")"); 570 } 571 return result.toString(); 572 } 573 } 574 575 public void setReplaceInputsWithConstants(boolean replaceInputsWithConstants) { 576 this.replaceInputsWithConstants = replaceInputsWithConstants; 577 } 578 }