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