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 */
23package org.graalvm.compiler.phases.common;
24
25import org.graalvm.compiler.core.common.GraalOptions;
26import org.graalvm.compiler.core.common.cfg.BlockMap;
27import org.graalvm.compiler.core.common.type.FloatStamp;
28import org.graalvm.compiler.core.common.type.Stamp;
29import org.graalvm.compiler.core.common.type.StampFactory;
30import org.graalvm.compiler.debug.CounterKey;
31import org.graalvm.compiler.debug.DebugContext;
32import org.graalvm.compiler.graph.Node;
33import org.graalvm.compiler.graph.NodeMap;
34import org.graalvm.compiler.graph.NodeStack;
35import org.graalvm.compiler.graph.Position;
36import org.graalvm.compiler.nodeinfo.InputType;
37import org.graalvm.compiler.nodes.AbstractBeginNode;
38import org.graalvm.compiler.nodes.AbstractMergeNode;
39import org.graalvm.compiler.nodes.BinaryOpLogicNode;
40import org.graalvm.compiler.nodes.ConstantNode;
41import org.graalvm.compiler.nodes.EndNode;
42import org.graalvm.compiler.nodes.IfNode;
43import org.graalvm.compiler.nodes.LogicNode;
44import org.graalvm.compiler.nodes.MergeNode;
45import org.graalvm.compiler.nodes.PhiNode;
46import org.graalvm.compiler.nodes.PiNode;
47import org.graalvm.compiler.nodes.StructuredGraph;
48import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult;
49import org.graalvm.compiler.nodes.UnaryOpLogicNode;
50import org.graalvm.compiler.nodes.ValueNode;
51import org.graalvm.compiler.nodes.ValuePhiNode;
52import org.graalvm.compiler.nodes.calc.BinaryNode;
53import org.graalvm.compiler.nodes.calc.ConditionalNode;
54import org.graalvm.compiler.nodes.calc.UnaryNode;
55import org.graalvm.compiler.nodes.cfg.Block;
56import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
57import org.graalvm.compiler.nodes.cfg.ControlFlowGraph.RecursiveVisitor;
58import org.graalvm.compiler.nodes.extended.IntegerSwitchNode;
59import org.graalvm.compiler.nodes.memory.FixedAccessNode;
60import org.graalvm.compiler.nodes.memory.FloatingAccessNode;
61import org.graalvm.compiler.nodes.memory.FloatingReadNode;
62import org.graalvm.compiler.nodes.memory.MemoryAccess;
63import org.graalvm.compiler.nodes.memory.MemoryPhiNode;
64import org.graalvm.compiler.nodes.util.GraphUtil;
65import org.graalvm.compiler.phases.BasePhase;
66import org.graalvm.compiler.phases.Phase;
67import org.graalvm.compiler.phases.graph.ScheduledNodeIterator;
68import org.graalvm.compiler.phases.schedule.SchedulePhase;
69import org.graalvm.compiler.phases.schedule.SchedulePhase.SchedulingStrategy;
70import org.graalvm.compiler.phases.tiers.LowTierContext;
71import org.graalvm.compiler.phases.tiers.PhaseContext;
72import org.graalvm.util.EconomicMap;
73import org.graalvm.util.MapCursor;
74
75import jdk.vm.ci.meta.Constant;
76import jdk.vm.ci.meta.MetaAccessProvider;
77import jdk.vm.ci.meta.TriState;
78
79/**
80 * This phase lowers {@link FloatingReadNode FloatingReadNodes} into corresponding fixed reads.
81 */
82public 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}
571