org.graalvm.compiler.nodes.loop.CountedLoopInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler Show documentation
Show all versions of compiler Show documentation
The GraalVM compiler and the Graal-truffle optimizer.
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.nodes.loop;
import static java.lang.Math.abs;
import static org.graalvm.compiler.nodes.calc.BinaryArithmeticNode.add;
import static org.graalvm.compiler.nodes.calc.BinaryArithmeticNode.sub;
import static org.graalvm.compiler.nodes.loop.MathUtil.unsignedDivBefore;
import org.graalvm.compiler.core.common.type.IntegerStamp;
import org.graalvm.compiler.core.common.type.Stamp;
import org.graalvm.compiler.core.common.type.StampFactory;
import org.graalvm.compiler.core.common.util.UnsignedLong;
import org.graalvm.compiler.debug.DebugCloseable;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.nodes.AbstractBeginNode;
import org.graalvm.compiler.nodes.ConstantNode;
import org.graalvm.compiler.nodes.GuardNode;
import org.graalvm.compiler.nodes.IfNode;
import org.graalvm.compiler.nodes.LogicConstantNode;
import org.graalvm.compiler.nodes.LogicNode;
import org.graalvm.compiler.nodes.LoopBeginNode;
import org.graalvm.compiler.nodes.NodeView;
import org.graalvm.compiler.nodes.PiNode;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.calc.CompareNode;
import org.graalvm.compiler.nodes.calc.ConditionalNode;
import org.graalvm.compiler.nodes.calc.NegateNode;
import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
import org.graalvm.compiler.nodes.cfg.HIRBlock;
import org.graalvm.compiler.nodes.extended.GuardingNode;
import org.graalvm.compiler.nodes.loop.InductionVariable.Direction;
import org.graalvm.compiler.nodes.util.IntegerHelper;
import org.graalvm.compiler.nodes.util.SignedIntegerHelper;
import org.graalvm.compiler.nodes.util.UnsignedIntegerHelper;
import jdk.vm.ci.meta.DeoptimizationAction;
import jdk.vm.ci.meta.DeoptimizationReason;
import jdk.vm.ci.meta.SpeculationLog;
/**
* Class representing meta information about a counted loop.
*
* Comments on the nomenclature for {@link #getLimit()}, {@link #getBody()},
* {@link #getLimitCheckedIV()} and {@link #getBodyIV()}:
*
* A regular head counted loop like
*
*
* for (int i = 0; i < end; i++) {
* // body
* }
*
*
* has a limit (end) that is compared against the {@link InductionVariable} (iv) returned by
* getLimitCheckedIV. The iv for the loop above is the basic induction variable i.
*
* For inverted loops like
*
*
* int i = 0;
* do {
* // body
* i++;
* } while(i < end)
*
*
* The iv compared against limit is not i, but the next iteration's body iv i+1.
*
* Thus, for inverted loops {@link #getBodyIV()} returns a different result than
* {@link #getLimitCheckedIV()}. {@link #getBodyIV()} returns i, while {@link #getLimitCheckedIV()}
* returns i + 1.
*
* Furthermore, the contract between {@link #getLimitCheckedIV()} and {@link #getBodyIV()} defines
* that both IVs iterate on the same signed-ness range, i.e., if one is purely in an unsigned range
* the other one has to be as well (same applies for signed integers). This means that optimizations
* can safely use {@link IntegerHelper} based on the signed-ness of the {@link #getLimitCheckedIV()}
* to compute min/max and iteration ranges for the loops involved.
*/
public class CountedLoopInfo {
protected final LoopEx loop;
protected InductionVariable limitCheckedIV;
protected ValueNode end;
/**
* {@code true} iff the limit is included in the limit test, e.g., the limit test is
* {@code i <= n} rather than {@code i < n}.
*/
protected boolean isLimitIncluded;
protected AbstractBeginNode body;
protected IfNode ifNode;
protected final boolean unsigned;
protected CountedLoopInfo(LoopEx loop, InductionVariable limitCheckedIV, IfNode ifNode, ValueNode end, boolean isLimitIncluded, AbstractBeginNode body, boolean unsigned) {
assert limitCheckedIV.direction() != null;
this.loop = loop;
this.limitCheckedIV = limitCheckedIV;
this.end = end;
this.isLimitIncluded = isLimitIncluded;
this.body = body;
this.ifNode = ifNode;
this.unsigned = unsigned;
}
/**
* @return the {@link InductionVariable} compared ({@link CompareNode}) to
* {@link CountedLoopInfo#getLimit()}. If this loop is
* {@link CountedLoopInfo#isInverted()} returns to next iteration iv based on
* {@link CountedLoopInfo#getBodyIV()}.
*/
public InductionVariable getLimitCheckedIV() {
return limitCheckedIV;
}
/**
* @return the {@link InductionVariable} used in the body of this {@link CountedLoopInfo}. If
* {@link CountedLoopInfo#isInverted()} returns {@code false} this returns the same as
* {@link CountedLoopInfo#getLimitCheckedIV()}.
*/
public InductionVariable getBodyIV() {
assert !isInverted() && getLimitCheckedIV() == limitCheckedIV : "Only inverted loops must have different body ivs.";
return limitCheckedIV;
}
/**
* Returns the limit node of this counted loop.
*
* @return the {@link ValueNode} that is compared ({@link CompareNode}) to the
* {@link InductionVariable} return by {@link CountedLoopInfo#getLimitCheckedIV()}
*/
public ValueNode getLimit() {
return end;
}
/**
* Returns the mathematical limit that is used to compute the
* {@link CountedLoopInfo#maxTripCountNode()}. If {@link CountedLoopInfo#isInverted()} is
* {@code false} this returns the same as {@link CountedLoopInfo#getLimit()}. Otherwise,
* depending on the shape of the inverted loops this may return a value that is |stride| off the
* real limit to account for inverted loops with none-inverted limit checks.
*
* Consider the following inverted loop
*
*
* int i = 0;
* do {
* i++;
* } while (i < 100);
*
*
* This loop performs 100 iterations. However, the following loop
*
*
* int i = 0;
* do {
* } while (i++ < 100);
*
*
* performs 101 iterations.
*
*
* While the "limit" of both is 100, the "real" mathematical limit of the second one is 101.
* Thus, in order to perform correct calculation of {@link CountedLoopInfo#maxTripCountNode()}
* we distinguish between those two concepts.
*/
public ValueNode getTripCountLimit() {
assert !isInverted() && getLimit() == end : "Only inverted loops must have a different trip count limit";
return end;
}
private void assertNoOverflow() {
GraalError.guarantee(loopCanNeverOverflow(), "Counter must never overflow when reasoning about trip counts of a loop");
}
/**
* Returns a node that computes the maximum trip count of this loop. That is the trip count of
* this loop assuming it is not exited by an other exit than the {@linkplain #getLimitTest()
* count check}.
*
* This count is exact if {@link #isExactTripCount()} returns true.
*
* THIS VALUE SHOULD BE TREATED AS UNSIGNED.
*/
public ValueNode maxTripCountNode() {
assertNoOverflow();
return maxTripCountNode(false);
}
public boolean isUnsignedCheck() {
return this.unsigned;
}
public ValueNode maxTripCountNode(boolean assumeLoopEntered) {
assertNoOverflow();
return maxTripCountNode(assumeLoopEntered, getCounterIntegerHelper());
}
protected ValueNode maxTripCountNode(boolean assumeLoopEntered, IntegerHelper integerHelper) {
assertNoOverflow();
return maxTripCountNode(assumeLoopEntered, integerHelper, getBodyIV().initNode(), getTripCountLimit());
}
/**
* Returns a node that computes the maximum trip count of this loop. That is the trip count of
* this loop assuming it is not exited by an other exit than the {@link #getLimitTest() count
* check}.
*
* This count is exact if {@link #isExactTripCount()} returns true.
*
* THIS VALUE SHOULD BE TREATED AS UNSIGNED.
*
* Warning: In order to calculate the max trip count it can be necessary to perform a devision
* operation in the generated code before the loop header. If {@code stride is not a power of 2}
* we have to perform an integer division of the range of the induction variable and the stride.
*
* @param assumeLoopEntered if true the check that the loop is entered at all will be omitted.
*
*/
public ValueNode maxTripCountNode(boolean assumeLoopEntered, IntegerHelper integerHelper, ValueNode initNode, ValueNode tripCountLimit) {
assertNoOverflow();
StructuredGraph graph = getBodyIV().valueNode().graph();
Stamp stamp = getBodyIV().valueNode().stamp(NodeView.DEFAULT);
ValueNode max;
ValueNode min;
ValueNode absStride;
final Direction direction = getBodyIV().direction();
if (direction == Direction.Up) {
absStride = getBodyIV().strideNode();
max = tripCountLimit;
min = initNode;
} else {
assert direction == Direction.Down : "direction must be down if its not up - else loop should not be counted " + direction;
absStride = NegateNode.create(getBodyIV().strideNode(), NodeView.DEFAULT);
max = initNode;
min = tripCountLimit;
}
ValueNode range = sub(max, min);
ConstantNode one = ConstantNode.forIntegerStamp(stamp, 1, graph);
if (isLimitIncluded) {
range = add(range, one);
}
// round-away-from-zero divison: (range + stride -/+ 1) / stride
ValueNode denominator = add(graph, range, sub(absStride, one), NodeView.DEFAULT);
/*
* While the divisor can never be zero because that would mean the direction of the loop is
* not strictly known which disables counted loop detection - it is possible that the stamp
* contains 0 though we know it effectively cannot. This happens when we have knowledge
* about the stride - for example its strictly positive or negative but not a constant - in
* both we cannot easily fold the negated stamp.
*
* Note that on certain architectures the division MIN/-1 also triggers a CPU divide error
* which has to be taken care of by the code generation via a state, thus actually deriving
* by stamps that this division can never trigger a divide error is very hard, and thus we
* refrain from doing so.
*/
final boolean divisorNonZero = true;
ValueNode div = unsignedDivBefore(graph, divisorNonZero, loop.entryPoint(), denominator, absStride, null);
if (assumeLoopEntered) {
return graph.addOrUniqueWithInputs(div);
}
ConstantNode zero = ConstantNode.forIntegerStamp(stamp, 0, graph);
// This check is "wide": it looks like min <= max
// That's OK even if the loop is strict (`!isLimitIncluded()`)
// because in this case, `div` will be zero when min == max
LogicNode noEntryCheck = graph.addOrUniqueWithInputs(integerHelper.createCompareNode(max, min, NodeView.DEFAULT));
ValueNode pi = findOrCreatePositivePi(noEntryCheck, div, graph, loop.loopsData().getCFG());
if (pi != null) {
return pi;
}
return graph.addOrUniqueWithInputs(ConditionalNode.create(noEntryCheck, zero, div, NodeView.DEFAULT));
}
/**
* Before creating a {@code ConditionalNode(noEntryCheck, zero, div)} node, check if the graph
* already contains a {@code !noEntryCheck} path dominating this loop, and build or reuse a
* {@link PiNode} there.
*
* @return a new or existing {@link PiNode} already added to the graph or {@code null}
*/
private ValueNode findOrCreatePositivePi(LogicNode noEntryCheck, ValueNode div, StructuredGraph graph, ControlFlowGraph cfg) {
Stamp positiveIntStamp = StampFactory.positiveInt();
if (!positiveIntStamp.isCompatible(div.stamp(NodeView.DEFAULT))) {
return null;
}
if (cfg.getNodeToBlock().isNew(loop.loopBegin())) {
return null;
}
HIRBlock loopBlock = cfg.blockFor(loop.loopBegin());
for (Node checkUsage : noEntryCheck.usages()) {
if (checkUsage instanceof IfNode) {
IfNode ifCheck = (IfNode) checkUsage;
if (cfg.getNodeToBlock().isNew(ifCheck.falseSuccessor())) {
continue;
}
if (cfg.blockFor(ifCheck.falseSuccessor()).dominates(loopBlock)) {
return graph.addOrUniqueWithInputs(PiNode.create(div, positiveIntStamp.improveWith(div.stamp(NodeView.DEFAULT)), ifCheck.falseSuccessor()));
}
}
}
return null;
}
/**
* Determine if the loop might be entered. Returns {@code false} if we can tell statically that
* the loop cannot be entered; returns {@code true} if the loop might possibly be entered,
* including in the case where we cannot be sure statically.
*
* @return false if the loop can definitely not be entered, true otherwise
*/
public boolean loopMightBeEntered() {
Stamp stamp = getBodyIV().valueNode().stamp(NodeView.DEFAULT);
ValueNode max;
ValueNode min;
if (getBodyIV().direction() == Direction.Up) {
max = getTripCountLimit();
min = getBodyIV().initNode();
} else {
assert getBodyIV().direction() == Direction.Down;
max = getBodyIV().initNode();
min = getTripCountLimit();
}
if (isLimitIncluded) {
// Ensure the constant is value numbered in the graph. Don't add other nodes to the
// graph, they will be dead code.
StructuredGraph graph = getBodyIV().valueNode().graph();
max = add(max, ConstantNode.forIntegerStamp(stamp, 1, graph), NodeView.DEFAULT);
}
LogicNode entryCheck = getCounterIntegerHelper().createCompareNode(min, max, NodeView.DEFAULT);
if (entryCheck.isContradiction()) {
// We can definitely not enter this loop.
return false;
} else {
// We don't know for sure that the loop can't be entered, so assume it can.
return true;
}
}
/**
* @return true if the loop has constant bounds.
*/
public boolean isConstantMaxTripCount() {
return getTripCountLimit() instanceof ConstantNode && getBodyIV().isConstantInit() && getBodyIV().isConstantStride();
}
public UnsignedLong constantMaxTripCount() {
assert isConstantMaxTripCount();
return new UnsignedLong(rawConstantMaxTripCount());
}
/**
* Compute the raw value of the trip count for this loop. THIS IS AN UNSIGNED VALUE;
*/
private long rawConstantMaxTripCount() {
assert getBodyIV().direction() != null;
long endValue = getTripCountLimit().asJavaConstant().asLong();
long initValue = getBodyIV().constantInit();
long range;
long absStride;
IntegerHelper helper = getCounterIntegerHelper(64);
if (getBodyIV().direction() == Direction.Up) {
if (helper.compare(endValue, initValue) < 0) {
return 0;
}
range = endValue - getBodyIV().constantInit();
absStride = getBodyIV().constantStride();
} else {
assert getBodyIV().direction() == Direction.Down;
if (helper.compare(initValue, endValue) < 0) {
return 0;
}
range = getBodyIV().constantInit() - endValue;
absStride = -getBodyIV().constantStride();
}
if (isLimitIncluded) {
range += 1;
}
long denominator = range + absStride - 1;
return Long.divideUnsigned(denominator, absStride);
}
public IntegerHelper getCounterIntegerHelper() {
IntegerStamp stamp = (IntegerStamp) getBodyIV().valueNode().stamp(NodeView.DEFAULT);
return getCounterIntegerHelper(stamp.getBits());
}
public IntegerHelper getCounterIntegerHelper(int bits) {
IntegerHelper helper;
if (isUnsignedCheck()) {
helper = new UnsignedIntegerHelper(bits);
} else {
helper = new SignedIntegerHelper(bits);
}
return helper;
}
public boolean isExactTripCount() {
return loop.loop().getNaturalExits().size() == 1;
}
public ValueNode exactTripCountNode() {
assertNoOverflow();
assert isExactTripCount();
return maxTripCountNode();
}
public boolean isConstantExactTripCount() {
assert isExactTripCount();
return isConstantMaxTripCount();
}
public UnsignedLong constantExactTripCount() {
assertNoOverflow();
assert isExactTripCount();
return constantMaxTripCount();
}
@Override
public String toString() {
return (isInverted() ? "Inverted " : "") + "iv=" + getLimitCheckedIV() + " until " + getTripCountLimit() + (isLimitIncluded ? getBodyIV().direction() == Direction.Up ? "+1" : "-1" : "") +
" bodyIV=" + getBodyIV();
}
/**
* @return the {@link IfNode} that checks {@link CountedLoopInfo#getLimitCheckedIV()} against
* {@link CountedLoopInfo#getLimit()}.
*/
public IfNode getLimitTest() {
return ifNode;
}
/**
* @return the {@link InductionVariable#initNode()} of the loop's {@link #getBodyIV()}, i.e.,
* the start node of the IV used inside the loop body (which can be different than the
* IV checked in {@link #getLimitCheckedIV()}}.
*/
public ValueNode getBodyIVStart() {
return getBodyIV().initNode();
}
public boolean isLimitIncluded() {
return isLimitIncluded;
}
public AbstractBeginNode getBody() {
return body;
}
public AbstractBeginNode getCountedExit() {
if (getLimitTest().trueSuccessor() == getBody()) {
return getLimitTest().falseSuccessor();
} else {
assert getLimitTest().falseSuccessor() == getBody();
return getLimitTest().trueSuccessor();
}
}
public Direction getDirection() {
return getLimitCheckedIV().direction();
}
public GuardingNode getOverFlowGuard() {
return loop.loopBegin().getOverflowGuard();
}
public boolean loopCanNeverOverflow() {
return counterNeverOverflows() || getOverFlowGuard() != null;
}
public boolean counterNeverOverflows() {
if (loop.loopBegin().canNeverOverflow()) {
return true;
}
if (!isLimitIncluded && getBodyIV().isConstantStride() && abs(getBodyIV().constantStride()) == 1) {
return true;
}
if (loop.loopBegin().isProtectedNonOverflowingUnsigned()) {
return true;
}
// @formatter:off
/*
* Following comment reasons about the simplest possible loop form:
*
* for(i = 0;i < end;i += stride)
*
* The problem is we want to create an overflow guard for the loop that can be hoisted
* before the loop, i.e., the overflow guard must not have loop variant inputs else it must
* be scheduled inside the loop. This means we cannot refer explicitly to the induction
* variable's phi but must establish a relation between end, stride and max (max integer
* range for a given loop) that is sufficient for most cases.
*
* We know that a head counted loop with a stride > 1 may overflow if the stride is big
* enough that end + stride will be > MAX, i.e. it overflows into negative value range.
*
* It is important that "end" in this context is the checked value of the loop condition:
* i.e., an arbitrary value. There is no relation between end and MAX established except
* that based on the integer representation we know that end <= MAX.
*
* A loop can overflow if the last checked value of the iv allows an overflow in the next
* iteration: the value range for which an overflow can happen is [MAX-(stride-1),MAX] e.g.
*
* MAX=10, stride = 3, overflow if number > 10
* end = MAX -> 10 -> 10 + 3 = 13 -> overflow
* end = MAX-1 -> 9 -> 9 + 3 = 12 -> overflow
* end = MAX-2 -> 8 -> 8 + 3 = 11 -> overflow
* end = MAX-3 -> 7 -> 7 + 3 = 10 -> No overflow at MAX - stride
*
* Note that this guard is pessimistic, i.e., it marks loops as potentially overflowing that
* are actually not overflowing. Consider the following loop:
*
*
* for(i = MAX-56; i < MAX, i += 8)
*
*
* where i in last loop body visit = MAX - 8, i after = MAX, no overflow
*
* which is wrongly detected as overflowing since "end" is element of [MAX-(stride-1),MAX]
* which is [MAX-7,MAX] and end is MAX. We handle such cases with a speculation and disable
* counted loop detection on subsequent compilations. We can only avoid such false positive
* detections by actually computing the number of iterations with a division, however we try
* to avoid that since that may be part of the fast path.
*
* And additional backup strategy could be to actually emit the precise guard inside the
* loop if the deopt already failed, but we refrain from this for now for simplicity
* reasons.
*/
// @formatter:on
IntegerStamp endStamp = (IntegerStamp) getTripCountLimit().stamp(NodeView.DEFAULT);
ValueNode strideNode = getBodyIV().strideNode();
IntegerStamp strideStamp = (IntegerStamp) strideNode.stamp(NodeView.DEFAULT);
IntegerHelper integerHelper = getCounterIntegerHelper();
if (getDirection() == Direction.Up) {
long max = integerHelper.maxValue();
return integerHelper.compare(endStamp.upperBound(), max - (strideStamp.upperBound() - 1) - (isLimitIncluded ? 1 : 0)) <= 0;
} else if (getDirection() == Direction.Down) {
long min = integerHelper.minValue();
return integerHelper.compare(min + (1 - strideStamp.lowerBound()) + (isLimitIncluded ? 1 : 0), endStamp.lowerBound()) <= 0;
}
return false;
}
@SuppressWarnings("try")
public GuardingNode createOverFlowGuard() {
GuardingNode overflowGuard = getOverFlowGuard();
if (overflowGuard != null || counterNeverOverflows()) {
return overflowGuard;
}
try (DebugCloseable position = loop.loopBegin().withNodeSourcePosition()) {
StructuredGraph graph = getBodyIV().valueNode().graph();
LogicNode cond = createOverflowGuardCondition();
SpeculationLog speculationLog = graph.getSpeculationLog();
SpeculationLog.Speculation speculation = SpeculationLog.NO_SPECULATION;
if (speculationLog != null) {
SpeculationLog.SpeculationReason speculationReason = LoopBeginNode.LOOP_OVERFLOW_DEOPT.createSpeculationReason(graph.method(), getBodyIV().loop.loopBegin().stateAfter().bci);
if (speculationLog.maySpeculate(speculationReason)) {
speculation = speculationLog.speculate(speculationReason);
LoopBeginNode.overflowSpeculationTaken.increment(graph.getDebug());
} else {
GraalError.shouldNotReachHere("Must not create overflow guard for loop " + loop.loopBegin() + " where the speculation guard already failed, this can create deopt loops"); // ExcludeFromJacocoGeneratedReport
}
}
assert graph.getGuardsStage().allowsFloatingGuards();
overflowGuard = graph.unique(new GuardNode(cond, AbstractBeginNode.prevBegin(loop.entryPoint()), DeoptimizationReason.LoopLimitCheck, DeoptimizationAction.InvalidateRecompile, true,
speculation, null));
loop.loopBegin().setOverflowGuard(overflowGuard);
return overflowGuard;
}
}
public LogicNode createOverflowGuardCondition() {
StructuredGraph graph = getBodyIV().valueNode().graph();
if (counterNeverOverflows()) {
return LogicConstantNode.contradiction(graph);
}
IntegerStamp stamp = (IntegerStamp) getBodyIV().valueNode().stamp(NodeView.DEFAULT);
IntegerHelper integerHelper = getCounterIntegerHelper();
LogicNode cond; // we use a negated guard with a < condition to achieve a >=
ConstantNode one = ConstantNode.forIntegerStamp(stamp, 1, graph);
if (getBodyIV().direction() == Direction.Up) {
ValueNode v1 = sub(ConstantNode.forIntegerStamp(stamp, integerHelper.maxValue()), sub(getBodyIV().strideNode(), one));
if (isLimitIncluded) {
v1 = sub(v1, one);
}
cond = graph.addOrUniqueWithInputs(integerHelper.createCompareNode(v1, getTripCountLimit(), NodeView.DEFAULT));
} else {
assert getBodyIV().direction() == Direction.Down;
ValueNode v1 = add(ConstantNode.forIntegerStamp(stamp, integerHelper.minValue()), sub(one, getBodyIV().strideNode()));
if (isLimitIncluded) {
v1 = add(v1, one);
}
cond = graph.addOrUniqueWithInputs(integerHelper.createCompareNode(getTripCountLimit(), v1, NodeView.DEFAULT));
}
return cond;
}
public IntegerStamp getStamp() {
return (IntegerStamp) getBodyIV().valueNode().stamp(NodeView.DEFAULT);
}
public boolean isInverted() {
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy