/*******************************************************************************
 * Copyright IBM Corp. and others 2000
 *
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which accompanies this
 * distribution and is available at https://www.eclipse.org/legal/epl-2.0/
 * or the Apache License, Version 2.0 which accompanies this distribution
 * and is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License, v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception [1] and GNU General Public
 * License, version 2 with the OpenJDK Assembly Exception [2].
 *
 * [1] https://www.gnu.org/software/classpath/license.html
 * [2] https://openjdk.org/legal/assembly-exception.html
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0
 *******************************************************************************/

#include <algorithm>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "codegen/CodeGenerator.hpp"
#include "env/FrontEnd.hpp"
#include "env/KnownObjectTable.hpp"
#include "codegen/RecognizedMethods.hpp"
#include "codegen/InstOpCode.hpp"
#include "compile/Compilation.hpp"
#include "compile/Method.hpp"
#include "compile/ResolvedMethod.hpp"
#include "compile/SymbolReferenceTable.hpp"
#include "compile/VirtualGuard.hpp"
#include "control/Options.hpp"
#include "control/Options_inlines.hpp"
#include "env/CompilerEnv.hpp"
#include "env/IO.hpp"
#include "env/ObjectModel.hpp"
#include "env/PersistentInfo.hpp"
#include "env/TRMemory.hpp"
#include "env/TypeLayout.hpp"
#include "env/jittypes.h"
#include "env/VMAccessCriticalSection.hpp"
#include "il/AliasSetInterface.hpp"
#include "il/AutomaticSymbol.hpp"
#include "il/Block.hpp"
#include "il/DataTypes.hpp"
#include "il/ILOpCodes.hpp"
#include "il/ILOps.hpp"
#include "il/MethodSymbol.hpp"
#include "il/Node.hpp"
#include "il/Node_inlines.hpp"
#include "il/ResolvedMethodSymbol.hpp"
#include "il/StaticSymbol.hpp"
#include "il/Symbol.hpp"
#include "il/SymbolReference.hpp"
#include "il/TreeTop.hpp"
#include "il/TreeTop_inlines.hpp"
#include "infra/Array.hpp"
#include "infra/Assert.hpp"
#include "infra/Bit.hpp"
#include "infra/Cfg.hpp"
#include "infra/Link.hpp"
#include "infra/List.hpp"
#include "infra/SimpleRegex.hpp"
#include "infra/CfgEdge.hpp"
#include "optimizer/Optimization_inlines.hpp"
#include "optimizer/OptimizationManager.hpp"
#include "optimizer/Optimizations.hpp"
#include "optimizer/Optimizer.hpp"
#include "optimizer/PreExistence.hpp"
#include "optimizer/TransformUtil.hpp"
#include "optimizer/UseDefInfo.hpp"
#include "optimizer/VPConstraint.hpp"
#include "optimizer/ValuePropagation.hpp"
#include "ras/Debug.hpp"
#include "ras/DebugCounter.hpp"
#include "runtime/Runtime.hpp"

#ifdef J9_PROJECT_SPECIFIC
#include "env/CHTable.hpp"
#include "env/PersistentCHTable.hpp"
#include "runtime/RuntimeAssumptions.hpp"
#include "env/VMJ9.h"
#include "optimizer/VPBCDConstraint.hpp"
#endif

#define OPT_DETAILS "O^O VALUE PROPAGATION: "

extern TR::Node *constrainChildren(OMR::ValuePropagation *vp, TR::Node *node);
extern TR::Node *constrainVcall(OMR::ValuePropagation *vp, TR::Node *node);

static void checkForNonNegativeAndOverflowProperties(OMR::ValuePropagation *vp, TR::Node *node,
    TR::VPConstraint *constraint = NULL)
{
    if (!constraint) {
        bool isGlobal;
        constraint = vp->getConstraint(node, isGlobal);
    }

    if (node->getOpCode().isLoad()) {
        node->setCannotOverflow(true);
        // dumpOptDetails(vp->comp(), "Load node %p cannot overflow\n", node);
    }

    if (constraint) {
        if (constraint->asIntConst()) {
            int32_t low = constraint->asIntConst()->getLowInt();
            if (low >= 0)
                node->setIsNonNegative(true);
            if (low <= 0)
                node->setIsNonPositive(true);
        }
        if (constraint->asLongConst()) {
            int64_t low = constraint->asLongConst()->getLowLong();
            if (low >= 0)
                node->setIsNonNegative(true);
            if (low <= 0)
                node->setIsNonPositive(true);
        }
        if (constraint->asShortConst()) {
            int16_t low = constraint->asShortConst()->getLowShort();
            if (low >= 0)
                node->setIsNonNegative(true);
            if (low <= 0)
                node->setIsNonPositive(true);
        }
        if (constraint->asIntRange()) {
            TR::VPIntRange *range = constraint->asIntRange();
            int32_t low = range->getLowInt();
            if (low >= 0)
                node->setIsNonNegative(true);

            int32_t high = range->getHighInt();
            if (high <= 0)
                node->setIsNonPositive(true);

            /// if ((node->getOpCode().isArithmetic() || node->getOpCode().isLoad()) &&
            ///       ((low > TR::getMinSigned<TR::Int32>()) || (high < TR::getMaxSigned<TR::Int32>())))
            if ((node->getOpCode().isLoad()
                    && ((low > TR::getMinSigned<TR::Int32>()) || (high < TR::getMaxSigned<TR::Int32>())))
                || (node->getOpCode().isArithmetic() && (range->canOverflow() != TR_yes))) {
                node->setCannotOverflow(true);
                // dumpOptDetails(vp->comp(), "Node %p cannot overflow\n", node);
            }
        } else if (constraint->asLongRange()) {
            TR::VPLongRange *range = constraint->asLongRange();
            int64_t low = range->getLowLong();
            if (low >= 0)
                node->setIsNonNegative(true);

            int64_t high = range->getHighLong();
            if (high <= 0)
                node->setIsNonPositive(true);

            /// if ((node->getOpCode().isArithmetic() || node->getOpCode().isLoad()) &&
            ///       ((low > TR::getMinSigned<TR::Int64>()) || (high < TR::getMaxSigned<TR::Int64>())))
            if ((node->getOpCode().isLoad()
                    && ((low > TR::getMinSigned<TR::Int64>()) || (high < TR::getMaxSigned<TR::Int64>())))
                || (node->getOpCode().isArithmetic() && (range->canOverflow() != TR_yes))) {
                node->setCannotOverflow(true);
                // dumpOptDetails(vp->comp(), "Node %p cannot overflow\n", node);
            }
        } else if (constraint->asShortRange()) {
            TR::VPShortRange *range = constraint->asShortRange();

            int16_t low = range->getLowShort();
            if (low >= 0)
                node->setIsNonNegative(true);

            int16_t high = range->getHighShort();
            if (high <= 0)
                node->setIsNonPositive(true);

            if ((node->getOpCode().isLoad()
                    && ((low > TR::getMinSigned<TR::Int16>()) || (high < TR::getMaxSigned<TR::Int16>())))
                || (node->getOpCode().isArithmetic() && (range->canOverflow() != TR_yes))) {
                node->setCannotOverflow(true);
            }
        }
    }
}

static bool isBoolean(TR::VPConstraint *constraint)
{
    if (constraint) {
        if (constraint->asIntConst()) {
            int32_t low = constraint->asIntConst()->getLowInt();
            if ((low >= 0) && (low <= 1))
                return true;
        }
        if (constraint->asLongConst()) {
            int64_t low = constraint->asLongConst()->getLowLong();
            if ((low >= 0) && (low <= 1))
                return true;
        }

        if (constraint->asShortConst()) {
            int16_t low = constraint->asShortConst()->getLowShort();
            if ((low >= 0) && (low <= 1))
                return true;
        }

        if (constraint->asIntRange()) {
            TR::VPIntRange *range = constraint->asIntRange();
            int32_t low = range->getLowInt();
            int32_t high = range->getHighInt();
            if ((low >= 0) && (high <= 1))
                return true;
        } else if (constraint->asLongRange()) {
            TR::VPLongRange *range = constraint->asLongRange();
            int64_t low = range->getLowLong();
            int64_t high = range->getHighLong();
            if ((low >= 0) && (high <= 1))
                return true;
        } else if (constraint->asShortRange()) {
            TR::VPShortRange *range = constraint->asShortRange();
            int16_t low = range->getLowShort();
            int16_t high = range->getHighShort();
            if ((low >= 0) && (high <= 1))
                return true;
        }
    }
    return false;
}

static int32_t arrayElementSize(const char *signature, int32_t len, TR::Node *node, OMR::ValuePropagation *vp)
{
    if (signature[0] != '[')
        return 0;

    // regular array element size
    if (signature[0] == '[') {
        switch (signature[1]) {
            case 'B':
                return 1;
            case 'C':
            case 'S':
                return 2;
            case 'I':
            case 'F':
                return 4;
            case 'D':
            case 'J':
                return 8;
            case 'Z':
                return static_cast<int32_t>(TR::Compiler->om.elementSizeOfBooleanArray());
            case 'L':
            default:
                return TR::Compiler->om.sizeofReferenceField();
        }
    }

    return 0;
}

// When node is successfully folded, isGlobal is set to true iff all
// constraints in the chain were global, and false otherwise.
static bool tryFoldCompileTimeLoad(OMR::ValuePropagation *vp, TR::Node *node, bool &isGlobal)
{
    isGlobal = true;
    if (!node->getOpCode().isLoad())
        return false;
    else if (node->getOpCode().isLoadReg())
        return false;
    else if (node->getSymbolReference()->isUnresolved())
        return false;

    if (node->getOpCode().isIndirect()) {
        if (vp->trace())
            traceMsg(vp->comp(), "  constrainCompileTimeLoad inspecting %s %p\n", node->getOpCode().getName(), node);

        TR::KnownObjectTable *knot = vp->comp()->getKnownObjectTable();
        TR::Node *baseExpression = NULL;
        TR::KnownObjectTable::Index baseKnownObject = TR::KnownObjectTable::UNKNOWN;
        TR::Node *curNode = node;

        for (; !baseExpression;) {
            // Track last curNode because its symref might be improved to ImmutableArrayShadow
            TR::Node *prevNode = curNode;

            // Look pass aladd/aiadd to get the array object for array shadows
            if (curNode->getSymbol()->isArrayShadowSymbol() && curNode->getFirstChild()->getOpCode().isArrayRef()
                && curNode->getFirstChild()->getSecondChild()->getOpCode().isLoadConst()) {
                curNode = curNode->getFirstChild()->getFirstChild();
            } else
                curNode = curNode->getFirstChild();

            bool curIsGlobal;
            TR::VPConstraint *constraint = vp->getConstraint(curNode, curIsGlobal);
            if (!curIsGlobal)
                isGlobal = false;
            if (!constraint) {
                if (vp->trace())
                    traceMsg(vp->comp(), "  - FAIL: %s %p has no constraint\n", curNode->getOpCode().getName(),
                        curNode);
                break;
            } else if (curNode->getOpCode().hasSymbolReference() && curNode->getSymbolReference()->isUnresolved()) {
                if (vp->trace())
                    traceMsg(vp->comp(), "  - FAIL: %s %p is unresolved\n", curNode->getOpCode().getName(), curNode);
                break;
            } else if (constraint->getKnownObject()) {
                TR::Symbol *prevNodeSym = prevNode->getSymbol();
                // Improve regular array shadows from arrays with immutable content
                if (prevNodeSym->isArrayShadowSymbol() &&
                    // UnsafeShadow responds true to above question, but the type of the array element might not match
                    // the type of the symbol, it needs to be derived from the array
                    !prevNodeSym->isUnsafeShadowSymbol()
                    && !vp->comp()->getSymRefTab()->isImmutableArrayShadow(prevNode->getSymbolReference())) {
                    if (constraint->getKnownObject()->isArrayWithConstantElements(vp->comp())) {
                        // Use the DataType of the symbol because it represents the element type of the array
                        TR::SymbolReference *improvedSymRef
                            = vp->comp()->getSymRefTab()->findOrCreateImmutableArrayShadowSymbolRef(
                                prevNodeSym->getDataType());
                        if (vp->trace())
                            traceMsg(vp->comp(), "Found arrayShadow load %p from array with constant elements %p\n",
                                prevNode, curNode);
                        if (performTransformation(vp->comp(), "%sUsing ImmutableArrayShadow symref #%d for node %p\n",
                                OPT_DETAILS, improvedSymRef->getReferenceNumber(), prevNode))
                            prevNode->setSymbolReference(improvedSymRef);

                        // Alias info of calls are computed in createAliasInfo, which is only called if alias info is
                        // not available or is invalid. Invalidate alias info so that it can be recomputed in the next
                        // optimization that needs it
                        vp->optimizer()->setAliasSetsAreValid(false);
                    } else if (!constraint->getKnownObject()->isArrayWithStableElements(vp->comp()))
                        break;
                }

                baseExpression = curNode;
                baseKnownObject = constraint->getKnownObject()->getIndex();
                if (vp->trace())
                    traceMsg(vp->comp(), "  - %s %p is obj%d\n", baseExpression->getOpCode().getName(), baseExpression,
                        baseKnownObject);
                break;
            } else if (knot && constraint->isConstString()) {
                baseExpression = curNode;
                baseKnownObject = knot->getOrCreateIndexAt((uintptr_t *)constraint->getConstString()
                                                               ->getSymRef()
                                                               ->getSymbol()
                                                               ->castToStaticSymbol()
                                                               ->getStaticAddress());
                if (vp->trace())
                    traceMsg(vp->comp(), "  - %s %p is string obj%d\n", baseExpression->getOpCode().getName(),
                        baseExpression, baseKnownObject);
                break;
            } else if (constraint->isJ9ClassObject() == TR_yes && constraint->isNonNullObject()
                && constraint->getClassType() && constraint->getClassType()->asFixedClass() && constraint->getClass()) {
                if (vp->trace())
                    traceMsg(vp->comp(), " - %s %p is class object - transforming\n", curNode->getOpCode().getName(),
                        curNode);
                TR::Node *nodeToRemove = NULL;
                uintptr_t clazz = (uintptr_t)constraint->getClass();
                bool didSomething
                    = TR::TransformUtil::transformIndirectLoadChainAt(vp->comp(), node, curNode, &clazz, &nodeToRemove);
                if (nodeToRemove) {
                    TR_ASSERT(didSomething,
                        "How can there be a node to remove if transformIndirectLoadChain didn't do something??");
                    vp->removeNode(nodeToRemove, true);
                    return true;
                }
                return didSomething;
            } else if (node->getSymbolReference() == vp->comp()->getSymRefTab()->findVftSymbolRef()
                && node->getFirstChild() == curNode && constraint->isNonNullObject() && constraint->getClassType()
                && constraint->getClassType()->asFixedClass() && constraint->getClass()) {
                TR_OpaqueClassBlock *clazz = constraint->getClass();
#ifdef J9_PROJECT_SPECIFIC
                TR_J9VMBase *fej9 = vp->comp()->fej9();
                // Non SVM AOT can deal with transformed loadaddr of system class only while Symbol Validation Manager
                // can handle any class. Skip the transformation under non SVM AOT when class is not loaded by bootstrap
                // class loader.
                // TODO: Disabling tranformation on Power for non SVM AOT because it does not add external relocation
                // record. Enable this once it is fixed.
                if (vp->comp()->compileRelocatableCode() && !vp->comp()->getOption(TR_UseSymbolValidationManager)
                    && (vp->comp()->target().cpu.isPower()
                        || fej9->getClassLoader(clazz) != fej9->getSystemClassLoader()))
                    return false;
#endif
                // performTransformation() is needed here but not in the other cases
                // because transformIndirectLoadChain[At]() does one internally.
                if (!performTransformation(vp->comp(),
                        "%sTransforming VFT load n%un [%p] to loadaddr "
                        "based on the fixed type of n%un [%p]\n",
                        OPT_DETAILS, node->getGlobalIndex(), node, curNode->getGlobalIndex(), curNode))
                    return false;

                node->setNumChildren(0);
                TR::Node::recreateWithSymRef(node, TR::loadaddr,
                    vp->comp()->getSymRefTab()->findOrCreateClassSymbol(vp->comp()->getMethodSymbol(), -1, clazz));
                node->setFlags(0);
                vp->removeNode(curNode, true);
                TR::DebugCounter::incStaticDebugCounter(vp->comp(),
                    TR::DebugCounter::debugCounterName(vp->comp(), "VFTLoadKnownObject/(%s)/%s",
                        vp->comp()->signature(), vp->comp()->getHotnessName(vp->comp()->getMethodHotness())));
                return true;
            } else if (!curNode->getOpCode().isLoadIndirect()) {
                if (curNode->getOpCodeValue() == TR::loadaddr
                    && curNode->getSymbolReference()->getSymbol()->isClassObject()
                    && !curNode->getSymbolReference()->isUnresolved()) {
                    TR::Node *nodeToRemove = NULL;
                    uintptr_t clazz
                        = (uintptr_t)curNode->getSymbolReference()->getSymbol()->getStaticSymbol()->getStaticAddress();
                    bool didSomething = TR::TransformUtil::transformIndirectLoadChainAt(vp->comp(), node, curNode,
                        &clazz, &nodeToRemove);
                    if (nodeToRemove) {
                        TR_ASSERT(didSomething,
                            "How can there be a node to remove if transformIndirectLoadChain didn't do something??");
                        vp->removeNode(nodeToRemove, true);
                        return true;
                    }
                    return didSomething;
                } else {
                    if (vp->trace())
                        traceMsg(vp->comp(),
                            "  - FAIL: %s %p is not an indirect load and has insufficient constraints\n",
                            curNode->getOpCode().getName(), curNode);
                    break;
                }
            }
#ifdef J9_PROJECT_SPECIFIC
            else if (vp->comp()->fej9()->isFinalFieldPointingAtJ9Class(curNode->getSymbolReference(), vp->comp())) {
                if (vp->trace())
                    traceMsg(vp->comp(), " - FAIL: %s %p points to a j9class but has insufficient constraints\n",
                        curNode->getOpCode().getName(), curNode);
                baseExpression = NULL;
                break;
            } else if (vp->comp()->fej9()->canDereferenceAtCompileTime(curNode->getSymbolReference(), vp->comp())) {
                // we can continue up the dereference chain
                if (vp->trace())
                    traceMsg(vp->comp(), "  - %s %p is %s\n", curNode->getOpCode().getName(), curNode,
                        curNode->getSymbolReference()->getName(vp->comp()->getDebug()));
                continue;
            }
#endif
            else {
                if (vp->trace())
                    traceMsg(vp->comp(), "  - FAIL: %s %p is %s\n", curNode->getOpCode().getName(), curNode,
                        curNode->getSymbolReference()->getName(vp->comp()->getDebug()));
                break;
            }
        }

        if (baseExpression) {
            // We've got something we can ask the front end to transform
            //
            TR::Node *nodeToRemove = NULL;
            bool didSomething = TR::TransformUtil::transformIndirectLoadChain(vp->comp(), node, baseExpression,
                baseKnownObject, &nodeToRemove);
            if (nodeToRemove) {
                TR_ASSERT(didSomething,
                    "How can there be a node to remove if transformIndirectLoadChain didn't do something??");
                vp->removeNode(nodeToRemove, true);
            }
            return didSomething;
        } else {
            return false;
        }
    } else {
        // Do direct loads too
        return false;
    }

    TR_ASSERT(0, "Shouldn't get here");
    return true; // Safe default
}

static bool constrainCompileTimeLoad(OMR::ValuePropagation *vp, TR::Node *node)
{
    bool isGlobal;
    if (!tryFoldCompileTimeLoad(vp, node, isGlobal))
        return false;

    // Add a constraint so that VP can continue with the constant value.
    constrainNewlyFoldedConst(vp, node, isGlobal);
    return true;
}

static bool findConstant(OMR::ValuePropagation *vp, TR::Node *node)
{
    // See if the node is already known to be a constant
    //
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node, isGlobal);
    if (constraint) {
        TR::DataType type = node->getDataType();
        switch (type) {
            case TR::Int64:
            case TR::Double:
                if (constraint->asLongConst()) {
                    bool replacedConst = false;
                    if (!vp->cg()->materializesLargeConstants() || (!node->getType().isInt64())
                        || (constraint->asLongConst()->getLong() < vp->cg()->getSmallestPosConstThatMustBeMaterialized()
                            && constraint->asLongConst()->getLong()
                                > vp->cg()->getLargestNegConstThatMustBeMaterialized())
                        || (vp->getCurrentParent()->getOpCode().isMul()
                            && (vp->getCurrentParent()->getSecondChild() == node)
                            && isNonNegativePowerOf2(constraint->asLongConst()->getLong()))) {
                        vp->replaceByConstant(node, constraint, isGlobal);
                        replacedConst = true;
                    }

                    if (constraint->getLowLong())
                        node->setIsNonZero(true);
                    else
                        node->setIsZero(true);
                    return replacedConst;
                }
                break;
            case TR::Address:
                if (constraint->isNullObject()) {
                    vp->replaceByConstant(node, constraint, isGlobal);
                    node->setIsNull(true);
                    return true;
                } else if (constraint->isNonNullObject()) {
                    node->setIsNonNull(true);
                    if (constraint->getKnownObject()) {
                        TR::VPKnownObject *knownObject = constraint->getKnownObject();
                        // Don't do direct loads here till we get the aliasing right
                        if (node->getOpCode().isLoadIndirect() && !node->getSymbolReference()->hasKnownObjectIndex()) {
                            TR::KnownObjectTable *knot = vp->comp()->getKnownObjectTable();
                            TR_ASSERT(knot, "Can't have a known-object constraint without a known-object table");
                            TR::SymbolReference *improvedSymRef
                                = vp->comp()->getSymRefTab()->findOrCreateSymRefWithKnownObject(
                                    node->getSymbolReference(), knownObject->getIndex());
                            if (improvedSymRef->hasKnownObjectIndex()) {
                                if (performTransformation(vp->comp(),
                                        "%sUsing known-object specific symref #%d for obj%d at [%p]\n", OPT_DETAILS,
                                        improvedSymRef->getReferenceNumber(), knownObject->getIndex(), node)) {
                                    node->setSymbolReference(improvedSymRef);
                                    return true;
                                }
                            }
                        } else if (node->getOpCode().isLoadDirect() && !node->hasKnownObjectIndex()
                            && !node->getSymbolReference()->hasKnownObjectIndex()
                            && node->getSymbolReference()->getSymbol()->isAutoOrParm()) {
                            if (performTransformation(vp->comp(), "%sSetting known-object obj%d on node [%p]\n",
                                    OPT_DETAILS, knownObject->getIndex(), node))
                                node->setKnownObjectIndex(knownObject->getIndex());
                        }
                    }
                }
                break;
            default:
                if (constraint->asIntConstraint()) {
                    int32_t value = constraint->getLowInt();
                    if (constraint->asIntConst()) {
                        bool replacedConst = false;
                        if (!vp->cg()->materializesLargeConstants() || (!node->getType().isInt32())
                            || (value < vp->cg()->getSmallestPosConstThatMustBeMaterialized()
                                && value > vp->cg()->getLargestNegConstThatMustBeMaterialized())
                            || (vp->getCurrentParent()->getOpCode().isMul()
                                && (vp->getCurrentParent()->getSecondChild() == node)
                                && isNonNegativePowerOf2(value))) {
                            vp->replaceByConstant(node, constraint, isGlobal);
                            replacedConst = true;
                        }
                        if (value)
                            node->setIsNonZero(true);
                        else
                            node->setIsZero(true);
                        return replacedConst;
                    } else if (value >= 0)
                        node->setIsNonNegative(true);

                    if (constraint->getHighInt() <= 0)
                        node->setIsNonPositive(true);

                    if ((node->getOpCode().isArithmetic() || node->getOpCode().isLoad())
                        && ((value > TR::getMinSigned<TR::Int32>())
                            || (constraint->getHighInt() < TR::getMaxSigned<TR::Int32>()))) {
                        node->setCannotOverflow(true);
                    }
                } else if (constraint->asShortConstraint()) {
                    int16_t value = constraint->getLowShort();
                    if (constraint->asShortConst()) {
                        bool replacedConst = false;
                        if (!vp->cg()->materializesLargeConstants() || (!node->getType().isInt16())
                            || (value < vp->cg()->getSmallestPosConstThatMustBeMaterialized()
                                && value > vp->cg()->getLargestNegConstThatMustBeMaterialized())
                            || (vp->getCurrentParent()->getOpCode().isMul()
                                && vp->getCurrentParent()->getSecondChild() == node && isNonNegativePowerOf2(value))) {
                            vp->replaceByConstant(node, constraint, isGlobal);
                            replacedConst = true;
                        }
                        if (value)
                            node->setIsNonZero(true);
                        else
                            node->setIsZero(true);
                        return replacedConst;
                    } else if (value >= 0)
                        node->setIsNonNegative(true);

                    if (constraint->getHighShort() <= 0)
                        node->setIsNonPositive(true);

                    if ((node->getOpCode().isArithmetic() || node->getOpCode().isLoad())
                        && ((value > TR::getMinSigned<TR::Int16>())
                            || (constraint->getHighShort() < TR::getMaxSigned<TR::Int16>()))) {
                        node->setCannotOverflow(true);
                    }
                }
                break;
        }
    }
    return false;
}

static bool refuseToConstrainUnsafe(OMR::ValuePropagation *vp, TR::Node *node, const char *why)
{
    if (vp->trace()) {
        traceMsg(vp->comp(), "Refusing to constrain unsafe access n%un [%p]: %s\n", node->getGlobalIndex(), node, why);
    }

    return true;
}

// Returns true to tell the handler to leave node unconstrained, or false to
// allow it to keep constraining. When the result is false, node may have been
// mutated.
static bool refineUnsafeAccess(OMR::ValuePropagation *vp, TR::Node *node)
{
    const bool okToConstrainNormally = false;
    TR::Compilation *comp = vp->comp();

    TR::SymbolReference *symRef = node->getSymbolReference();
    if (symRef->isLitPoolReference())
        return refuseToConstrainUnsafe(vp, node, "literal pool reference");

    TR::Symbol *sym = symRef->getSymbol();
    if (!sym->isShadow())
        return okToConstrainNormally;

    if (symRef == vp->getSymRefTab()->findInstanceShapeSymbolRef()
        || symRef == vp->getSymRefTab()->findInstanceDescriptionSymbolRef()
        || symRef == vp->getSymRefTab()->findDescriptionWordFromPtrSymbolRef()
#ifdef J9_PROJECT_SPECIFIC
        || symRef == vp->getSymRefTab()->findClassFromJavaLangClassAsPrimitiveSymbolRef()
#endif
        || sym == comp->getSymRefTab()->findGenericIntShadowSymbol()) {
        return refuseToConstrainUnsafe(vp, node, "special symref other than unsafe shadow");
    }

    if (!sym->isUnsafeShadowSymbol())
        return okToConstrainNormally;

    static const bool disable = feGetEnv("TR_disableUnsafeShadowRefinement") != NULL;

    if (disable)
        return refuseToConstrainUnsafe(vp, node, "unsafe shadow refinement is disabled");

    if (!sym->isTransparent() && !sym->isVolatile())
        return refuseToConstrainUnsafe(vp, node, "non-transparent non-volatile symbol");

    if (vp->trace()) {
        traceMsg(comp, "Found unsafe shadow access n%un [%p]\n", node->getGlobalIndex(), node);
    }

    if (comp->compileRelocatableCode() && !comp->getOption(TR_UseSymbolValidationManager)) {
        return refuseToConstrainUnsafe(vp, node, "AOT without SVM");
    }

    if (symRef->getOffset() != 0)
        return refuseToConstrainUnsafe(vp, node, "shadow symref has nonzero offset");

    TR::Node *addr = node->getChild(0);
    TR::ILOpCodes addrOp = addr->getOpCodeValue();
    if (addrOp != TR::aladd && addrOp != TR::aiadd)
        return refuseToConstrainUnsafe(vp, node, "address not from an offset op");

    TR::Node *obj = addr->getChild(0);
    bool objIsGlobal = false;
    TR::VPConstraint *objConstraint = vp->getConstraint(obj, objIsGlobal);
    if (objConstraint == NULL)
        return refuseToConstrainUnsafe(vp, node, "unconstrained base object");

    TR_OpaqueClassBlock *objClass = NULL;
    if (objConstraint->isClassObject() != TR_yes) {
        objClass = objConstraint->getClass();
    } else {
        // objConstraint->getClass() is a bound on the *represented* class. If
        // the base happens to be a known object, try to get its type that way.
        TR::VPKnownObject *knownObj = objConstraint->getKnownObject();
        if (knownObj != NULL) {
            TR::KnownObjectTable::Index koi = knownObj->getIndex();
            objClass = comp->fe()->getObjectClassFromKnownObjectIndex(comp, koi);
        }
    }

    if (objClass == NULL)
        return refuseToConstrainUnsafe(vp, node, "unknown-type base object");

    int32_t objClassSigLen = 0;
    const char *objClassSig = TR::VPResolvedClass::create(vp, objClass)->getClassSignature(objClassSigLen);

    if (vp->trace()) {
        traceMsg(comp, "Base object type is %p %.*s\n", objClass, objClassSigLen, objClassSig);
    }

    TR::DataTypes nodeType = node->getOpCode().getDataType().getDataType();
    TR::SymbolReferenceTable *srTab = comp->getSymRefTab();

    if (TR::Compiler->cls.isClassArray(comp, objClass)) {
        if (vp->trace())
            traceMsg(comp, "Base object is an array\n");

        TR::DataTypes elemType = TR::NoType;
        if (TR::Compiler->cls.isPrimitiveArray(comp, objClass))
            elemType = TR::Compiler->cls.primitiveArrayComponentType(comp, objClass);
        else if (TR::Compiler->cls.isReferenceArray(comp, objClass))
            elemType = TR::Address;

        if (elemType == TR::NoType)
            return refuseToConstrainUnsafe(vp, node, "unknown array component type");

        if (elemType != nodeType) {
            static const bool dontRefineTypePun = feGetEnv("TR_dontRefineUnsafeToTypePunnedArrayShadow") != NULL;

            if (dontRefineTypePun)
                return refuseToConstrainUnsafe(vp, node, "type-punned array access");
        }

        if (!sym->isTransparent()) {
            // We don't have a representation for non-transparent array shadows, and it's
            // not straightforward to add one because we'd have to move non-transparent
            // status to SymbolReference from Symbol.
            return refuseToConstrainUnsafe(vp, node, "non-transparent array access");
        }

        TR::SymbolReference *arrayShadow = srTab->findOrCreateArrayShadowSymbolRef(elemType, obj);

        if (!performTransformation(comp, "%sRefine unsafe shadow access n%un [%p] to %s array shadow #%d\n",
                OPT_DETAILS, node->getGlobalIndex(), node, TR::DataType::getName(elemType),
                arrayShadow->getReferenceNumber()))
            return refuseToConstrainUnsafe(vp, node, "performTransformation denied");

        node->setSymbolReference(arrayShadow);

        // With the array-shadow symref replacing the unsafe shadow symref, the existing alias sets may no
        // longer be valid. We need to mark the current alias sets as invalid to trigger a recomputation of the
        // alias sets the next time they are needed.
        vp->optimizer()->setAliasSetsAreValid(false);
        return okToConstrainNormally;
    }

    const TR::TypeLayout *layout = comp->typeLayout(objClass);
    if (layout == NULL)
        return refuseToConstrainUnsafe(vp, node, "missing type layout");

    TR::Node *offset = addr->getChild(1);
    if (!offset->getOpCode().isLoadConst())
        return refuseToConstrainUnsafe(vp, node, "non-constant offset");

    int64_t offsetValueSigned = offset->getConstValue();
    if (offsetValueSigned < 0)
        return refuseToConstrainUnsafe(vp, node, "negative offset");

    uint64_t offsetValue = (uint64_t)offsetValueSigned;
    const TR::TypeLayoutEntry *field = NULL;
    size_t i = 0;
    for (; i < layout->count(); i++) {
        field = &layout->entry(i);
        if (field->_offset == offsetValue && field->_datatype.getDataType() == nodeType)
            break;
    }

    if (i >= layout->count())
        return refuseToConstrainUnsafe(vp, node, "no matching field");

    if (field->_isVolatile != sym->isVolatile()) {
        // We don't have a representation for field shadows with the unexpected
        // volatility, and it's not straightforward to add one because we'd have
        // to move volatile status to SymbolReference from Symbol.
        //
        // Functionally it would be fine to go on and refine anyway if the field
        // is volatile. Doing so would still improve aliasing, but it would make
        // the access itself more expensive, so just leave it alone for now.
        //
        const char *mismatch
            = field->_isVolatile ? "non-volatile access to volatile field" : "volatile access to non-volatile field";

        return refuseToConstrainUnsafe(vp, node, mismatch);
    }

    TR::SymbolReference *refinedSymRef = srTab->findOrFabricateShadowSymbol(objClass, field->_datatype, field->_offset,
        field->_isVolatile, field->_isPrivate, field->_isFinal, field->_fieldname, field->_typeSignature);

    if (!performTransformation(comp, "%sRefine unsafe shadow access n%un [%p] to field shadow #%d %.*s.%s %s\n",
            OPT_DETAILS, node->getGlobalIndex(), node, refinedSymRef->getReferenceNumber(), objClassSigLen, objClassSig,
            field->_fieldname, field->_typeSignature))
        return refuseToConstrainUnsafe(vp, node, "performTransformation denied");

    node->setSymbolReference(refinedSymRef);
    node->setAndIncChild(0, obj);
    addr->recursivelyDecReferenceCount();
    return okToConstrainNormally;
}

static bool owningMethodDoesNotContainNullChecks(OMR::ValuePropagation *vp, TR::Node *node)
{
    TR::ResolvedMethodSymbol *method = node->getSymbolReference()->getOwningMethodSymbol(vp->comp());
    if (method && method->skipNullChecks())
        return true;
    return false;
}

bool constraintFitsInIntegerRange(OMR::ValuePropagation *vp, TR::VPConstraint *constraint)
{
    if (constraint == NULL)
        return false;

    TR::VPLongConstraint *longConstraint = constraint->asLongConstraint();
    TR::VPIntConstraint *intConstraint = constraint->asIntConstraint();
    TR::VPShortConstraint *shortConstraint = constraint->asShortConstraint();

    if (longConstraint) {
        int64_t low = longConstraint->getLowLong(), high = longConstraint->getHighLong();
        if (low >= TR::getMinSigned<TR::Int32>() && high <= TR::getMaxSigned<TR::Int32>())
            return true;
    } else if (intConstraint || shortConstraint)
        return true;

    return false;
}

bool canMoveLongOpChildDirectly(TR::Node *node, int32_t numChild, TR::Node *arithNode)
{
    return node->getChild(numChild)->getDataType() == arithNode->getDataType()
        || (node->getOpCodeValue() == TR::lshr && numChild > 0);
}

bool reduceLongOpToIntegerOp(OMR::ValuePropagation *vp, TR::Node *node, TR::VPConstraint *nodeConstraint)
{
    if (!constraintFitsInIntegerRange(vp, nodeConstraint))
        return false;

    for (int32_t i = 0; i < node->getNumChildren(); i++) {
        bool isGlobal;
        TR::VPConstraint *childConstraint = vp->getConstraint(node->getChild(i), isGlobal);
        if (!constraintFitsInIntegerRange(vp, childConstraint))
            return false;
    }

    // Adding this check because it is likely suboptimal on machines where you can use long registers to convert things
    // to integers and insert conversion trees. Conversion trees are not free. LoadExtensions (and other codegen
    // peepholes) should in theory catch uneeded conversions (like widening) as described in the case below on 64 bit
    // platforms.
    if (vp->comp()->target().is64Bit() || vp->comp()->cg()->use64BitRegsOn32Bit())
        return false;

    // The node and all children have constraints that fit in integer range.
    // lrem          becomes i2l
    //   i2l                   irem (new node)
    //     iload                 iload (get rid of the l2i)
    //   lconst                l2i (new node)
    //                           lconst
    TR::ILOpCodes newOp = TR::ILOpCode::integerOpForLongOp(node->getOpCodeValue());
    if (newOp == TR::BadILOp)
        return false;

    if (performTransformation(vp->comp(), "%sReduce %s (0x%p) to integer arithmetic\n", OPT_DETAILS,
            node->getOpCode().getName(), node)) {
        TR::Node *arithNode = TR::Node::create(node, newOp, node->getNumChildren());

        for (int32_t i = 0; i < node->getNumChildren(); i++) {
            TR::Node *child = node->getChild(i);
            if (canMoveLongOpChildDirectly(node, i, arithNode)) {
                arithNode->setAndIncChild(i, child);
                dumpOptDetails(vp->comp(), "    Transfer integer child %d %s (0x%p)\n", i, child->getOpCode().getName(),
                    child);
            } else if (child->getOpCode().isConversion()
                && child->getFirstChild()->getDataType() == arithNode->getDataType()) {
                if (child->getReferenceCount() > 1)
                    vp->anchorNode(child, vp->_curTree);
                arithNode->setAndIncChild(i, child->getFirstChild());
                dumpOptDetails(vp->comp(), "    Replacing child %d %s (0x%p) with grandchild %s (0x%p)\n", i,
                    child->getOpCode().getName(), child, child->getFirstChild()->getOpCode().getName(),
                    child->getFirstChild());
            } else {
                TR::Node *newChild = TR::Node::create(node,
                    TR::ILOpCode::getProperConversion(child->getDataType(), arithNode->getDataType(), false), 1);
                newChild->setAndIncChild(0, child);
                arithNode->setAndIncChild(i, newChild);
                dumpOptDetails(vp->comp(), "    Creating new %s (0x%p) above child %d %s (0x%p)\n",
                    newChild->getOpCode().getName(), newChild, i, child->getOpCode().getName(), child);
            }
        }

        vp->prepareToReplaceNode(node,
            TR::ILOpCode::getProperConversion(arithNode->getDataType(), node->getDataType(), false));
        node->setNumChildren(1);
        node->setAndIncChild(0, arithNode);
        dumpOptDetails(vp->comp(), "  Changed (0x%p) to %s with new child %s (0x%p)\n", node,
            node->getOpCode().getName(), arithNode->getOpCode().getName(), arithNode);
        return true;
    }

    return false;
}

static void constrainAConst(OMR::ValuePropagation *vp, TR::Node *node, bool isGlobal)
{
    TR::VPConstraint *constraint = NULL;
    if (node->getAddress() == 0) {
        constraint = TR::VPNullObject::create(vp);
        node->setIsNull(true);
    } else {
        constraint = TR::VPNonNullObject::create(vp);
        node->setIsNonNull(true);
        if (node->isClassPointerConstant()) {
            TR::VPConstraint *typeConstraint
                = TR::VPClass::create(vp, TR::VPFixedClass::create(vp, (TR_OpaqueClassBlock *)node->getAddress()), NULL,
                    NULL, NULL, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject));
            vp->addBlockOrGlobalConstraint(node, typeConstraint, isGlobal);
        }
    }
    vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
}

TR::Node *constrainAConst(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainAConst(vp, node, true /* isGlobal */);
    return node;
}

static void constrainIntAndFloatConstHelper(OMR::ValuePropagation *vp, TR::Node *node, int32_t value, bool isGlobal)
{
    if (value) {
        node->setIsNonZero(true);
        if (value & TR::getMinSigned<TR::Int32>())
            node->setIsNonPositive(true);
        else
            node->setIsNonNegative(true);
    } else {
        node->setIsZero(true);
        node->setIsNonNegative(true);
        node->setIsNonPositive(true);
    }

    vp->addBlockOrGlobalConstraint(node, TR::VPIntConst::create(vp, value), isGlobal);
}

static void constrainIntConst(OMR::ValuePropagation *vp, TR::Node *node, bool isGlobal)
{
    int32_t value = node->getInt();
    constrainIntAndFloatConstHelper(vp, node, value, isGlobal);
}

TR::Node *constrainIntConst(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainIntConst(vp, node, true /* isGlobal */);
    return node;
}

TR::Node *constrainFloatConst(OMR::ValuePropagation *vp, TR::Node *node)
{
    int32_t value = node->getFloatBits();
    constrainIntAndFloatConstHelper(vp, node, value, true /* isGlobal */);
    return node;
}

static void constrainLongConst(OMR::ValuePropagation *vp, TR::Node *node, bool isGlobal)
{
    int64_t value = node->getLongInt();
    if (value) {
        node->setIsNonZero(true);
        if (value & TR::getMinSigned<TR::Int64>())
            node->setIsNonPositive(true);
        else
            node->setIsNonNegative(true);
    } else {
        node->setIsZero(true);
        node->setIsNonNegative(true);
        node->setIsNonPositive(true);
    }

    vp->addBlockOrGlobalConstraint(node, TR::VPLongConst::create(vp, value), isGlobal);
}

TR::Node *constrainLongConst(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainLongConst(vp, node, true /* isGlobal */);
    return node;
}

TR::Node *constrainByteConst(OMR::ValuePropagation *vp, TR::Node *node)
{
    int8_t value = node->getByte();
    if (value) {
        node->setIsNonZero(true);
        if (value & TR::getMinSigned<TR::Int8>())
            node->setIsNonPositive(true);
        else
            node->setIsNonNegative(true);
    } else {
        node->setIsZero(true);
        node->setIsNonNegative(true);
        node->setIsNonPositive(true);
    }

    // treat constants as always signed. its upto the consumer
    // to decide if a sign-extension or zero-extension (for unsigned
    // datatypes like c2i, bu2i) is needed
    // also donot generate a new global constraint at this point if
    // one is already available.
    //
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node, isGlobal);
    if (!constraint)
        vp->addGlobalConstraint(node, TR::VPIntConst::create(vp, value));
    return node;
}

TR::Node *constrainShortConst(OMR::ValuePropagation *vp, TR::Node *node)
{
    int16_t value = node->getShortInt();
    if (value) {
        node->setIsNonZero(true);
        if (value & TR::getMinSigned<TR::Int16>())
            node->setIsNonPositive(true);
        else
            node->setIsNonNegative(true);
    } else {
        node->setIsZero(true);
        node->setIsNonNegative(true);
        node->setIsNonPositive(true);
    }

    // see comments above for byteConst
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node, isGlobal);
    if (!constraint)
        vp->addGlobalConstraint(node, TR::VPShortConst::create(vp, value));
    return node;
}

// constrainBCDSign tracks 3 ways a known sign can be retrieved from a node
// 1)    via a specific known sign tracked on the node itself (this could be for any type of BCD node)
// 2a)   for a setsign operation where the setsign value is on the node itself
// 2b)   for a setsign operation where the setsign value is a const child of the node
//
#ifdef J9_PROJECT_SPECIFIC
#define TR_ENABLE_BCD_SIGN (true)

TR::Node *constrainBCDSign(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (!TR_ENABLE_BCD_SIGN)
        return node;

    int32_t sign = TR::DataType::getInvalidSignCode();
    if (node->hasKnownSignCode()) // TODO: track assumed sign codes too? -- these are tracked on load operations already
                                  // so not much benefit
    {
        TR_RawBCDSignCode rawSign = node->getKnownSignCode();
        sign = TR::DataType::getValue(rawSign);
        if (vp->trace())
            traceMsg(vp->comp(), "\tconstrainBCDSign from knownSign : %s (%p) sign %s (0x%x)\n",
                node->getOpCode().getName(), node, TR::DataType::getName(rawSign), sign);
    } else if (node->getOpCode().isSetSignOnNode()) {
        TR_RawBCDSignCode rawSign = node->getSetSign();
        sign = TR::DataType::getValue(rawSign);
        if (vp->trace())
            traceMsg(vp->comp(), "\tconstrainBCDSign from setSignOnNode : %s (%p) sign %s (0x%x)\n",
                node->getOpCode().getName(), node, TR::DataType::getName(rawSign), sign);
    } else if (node->getOpCode().isSetSign()) {
        TR::Node *setSignValue = node->getSetSignValueNode();
        if (setSignValue->getOpCode().isLoadConst() && setSignValue->getType().isIntegral()
            && setSignValue->getSize() <= 4) {
            sign = setSignValue->get32bitIntegralValue();
            if (vp->trace())
                traceMsg(vp->comp(), "\tconstrainBCDSign from setSignOp : %s (%p) sign 0x%x\n",
                    node->getOpCode().getName(), node, sign);
        }
    }

    if (sign == TR::DataType::getInvalidSignCode()
        || sign == TR::DataType::getIgnoredSignCode()) // if no specific sign code see if a clean or preferred sign
                                                       // state can be tracked
    {
        TR_BCDSignConstraint constraintType = TR_Sign_Unknown;
        if (node->hasKnownCleanSign())
            constraintType = TR_Sign_Clean;
        else if (node->hasKnownPreferredSign())
            constraintType = TR_Sign_Preferred;

        if (constraintType != TR_Sign_Unknown) {
            if (vp->trace())
                traceMsg(vp->comp(), "\tnode %s (%p) got clean or preferred constraintType %s\n",
                    node->getOpCode().getName(), node, TR::VP_BCDSign::getName(constraintType));
            vp->addGlobalConstraint(node, TR::VP_BCDSign::create(vp, constraintType, node->getDataType()));
        }
    } else {
        TR_BCDSignCode normalizedSign = TR::DataType::getNormalizedSignCode(node->getDataType(), sign);
        TR_BCDSignConstraint constraintType = TR::VP_BCDSign::getSignConstraintFromBCDSign(normalizedSign);

        if (vp->trace())
            traceMsg(vp->comp(), "\tnode %s (%p) got constraintType %s for sign 0x%x\n", node->getOpCode().getName(),
                node, TR::VP_BCDSign::getName(constraintType), sign);

        if (constraintType == TR_Sign_Minus && node->hasKnownCleanSign()) {
            if (vp->trace())
                traceMsg(vp->comp(), "\tpromote constraintType %s->%s as node %s (%p) is clean\n",
                    TR::VP_BCDSign::getName(constraintType), TR::VP_BCDSign::getName(TR_Sign_Minus_Clean),
                    node->getOpCode().getName(), node);
            constraintType = TR_Sign_Minus_Clean;
        }

        if (constraintType != TR_Sign_Unknown)
            vp->addGlobalConstraint(node, TR::VP_BCDSign::create(vp, constraintType, node->getDataType()));
    }

    return node;
}
#endif

TR::Node *constrainBCDAggrLoad(OMR::ValuePropagation *vp, TR::Node *node)
{
#ifdef J9_PROJECT_SPECIFIC
    TR::Node *parent = vp->getCurrentParent();

    if (findConstant(vp, node))
        return node;

    constrainChildren(vp, node);

    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node, isGlobal);
    TR::VP_BCDSign *signConstraint = constraint ? constraint->asBCDSign() : NULL;
    if (signConstraint && TR_ENABLE_BCD_SIGN && signConstraint->getDataType() == node->getDataType()) {
        switch (signConstraint->getSign()) {
            case TR_Sign_Clean:
                if (!node->hasKnownCleanSign()
                    && performTransformation(vp->comp(), "%sTransfer sign constraint %s to %s (0x%p)\n", OPT_DETAILS,
                        signConstraint->getName(), node->getOpCode().getName(), node)) {
                    if (vp->comp()->cg()->traceBCDCodeGen())
                        traceMsg(vp->comp(), "y^y: VP: Transfer sign constraint %s to %s (0x%p)\n",
                            signConstraint->getName(), node->getOpCode().getName(), node);
                    node->setHasKnownCleanSign(true);
                }
                break;
            case TR_Sign_Preferred:
                if (!node->hasKnownPreferredSign()
                    && performTransformation(vp->comp(), "%sTransfer sign constraint %s to %s (0x%p)\n", OPT_DETAILS,
                        signConstraint->getName(), node->getOpCode().getName(), node)) {
                    if (vp->comp()->cg()->traceBCDCodeGen())
                        traceMsg(vp->comp(), "y^y: VP: Transfer sign constraint %s to %s (0x%p)\n",
                            signConstraint->getName(), node->getOpCode().getName(), node);
                    node->setHasKnownPreferredSign(true);
                }
                break;
            case TR_Sign_Plus:
                if (!node->knownSignCodeIs(bcd_plus)
                    && performTransformation(vp->comp(), "%sTransfer sign constraint %s to %s (0x%p)\n", OPT_DETAILS,
                        signConstraint->getName(), node->getOpCode().getName(), node)) {
                    if (vp->comp()->cg()->traceBCDCodeGen())
                        traceMsg(vp->comp(), "y^y: VP: Transfer sign constraint %s to %s (0x%p)\n",
                            signConstraint->getName(), node->getOpCode().getName(), node);
                    node->setKnownSignCode(bcd_plus);
                }
                break;
            case TR_Sign_Minus:
                if (!node->knownSignCodeIs(bcd_minus)
                    && performTransformation(vp->comp(), "%sTransfer sign constraint %s to %s (0x%p)\n", OPT_DETAILS,
                        signConstraint->getName(), node->getOpCode().getName(), node)) {
                    if (vp->comp()->cg()->traceBCDCodeGen())
                        traceMsg(vp->comp(), "y^y: VP: Transfer sign constraint %s to %s (0x%p)\n",
                            signConstraint->getName(), node->getOpCode().getName(), node);
                    node->setKnownSignCode(bcd_minus);
                }
                break;
            case TR_Sign_Unsigned:
                if (!node->knownSignCodeIs(bcd_unsigned)
                    && performTransformation(vp->comp(), "%sTransfer sign constraint %s to %s (0x%p)\n", OPT_DETAILS,
                        signConstraint->getName(), node->getOpCode().getName(), node)) {
                    if (vp->comp()->cg()->traceBCDCodeGen())
                        traceMsg(vp->comp(), "y^y: VP: Transfer sign constraint %s to %s (0x%p)\n",
                            signConstraint->getName(), node->getOpCode().getName(), node);
                    node->setKnownSignCode(bcd_unsigned);
                }
                break;
            case TR_Sign_Minus_Clean:
                if (!(node->knownSignCodeIs(bcd_minus) && node->hasKnownCleanSign())
                    && performTransformation(vp->comp(), "%sTransfer sign constraint %s to %s (0x%p)\n", OPT_DETAILS,
                        signConstraint->getName(), node->getOpCode().getName(), node)) {
                    if (vp->comp()->cg()->traceBCDCodeGen())
                        traceMsg(vp->comp(), "y^y: VP: Transfer sign constraint %s to %s (0x%p)\n",
                            signConstraint->getName(), node->getOpCode().getName(), node);
                    node->setKnownSignCode(bcd_minus);
                    node->setHasKnownCleanSign(true);
                }
                break;
            default:
                break;
        }
    }

    if (node->getOpCode().isIndirect() && !vp->_curTree->getNode()->getOpCode().isNullCheck()
        && owningMethodDoesNotContainNullChecks(vp, node)) {
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));
    }
#endif
    return node;
}

TR::Node *constrainAnyIntLoad(OMR::ValuePropagation *vp, TR::Node *node)
{
    TR::DataType dataType = node->getDataType();
    // Optimize characters being loaded out of the values array of a constant string
    //
    if (node->getOpCode().isIndirect() && node->getSymbol()->isArrayShadowSymbol()
        && node->getFirstChild()->getOpCode().isAdd()) {
        TR::Node *array = node->getFirstChild()->getFirstChild();
        TR::Node *index = node->getFirstChild()->getSecondChild();
        if (index->getOpCode().isLoadConst()) {
            bool isGlobal;
            if (dataType == TR::Int16 && array->getOpCode().isIndirect()) {
                TR::Node *base = array->getFirstChild();
                TR::VPConstraint *baseVPConstraint = vp->getConstraint(base, isGlobal);
                if (baseVPConstraint && baseVPConstraint->isConstString()) {
                    TR::VPConstString *constString = baseVPConstraint->getClassType()->asConstString();

                    // Get index from offset of a 2-byte element array
                    uintptr_t offset = vp->comp()->target().is64Bit() ? (uintptr_t)index->getUnsignedLongInt()
                                                                      : (uintptr_t)index->getUnsignedInt();
                    uintptr_t chIdx = 0;
                    if (array->isDataAddrPointer()) {
                        chIdx = (offset) / 2;
                    } else {
                        chIdx = (offset - (uintptr_t)TR::Compiler->om.contiguousArrayHeaderSizeInBytes()) / 2;
                    }
                    uint16_t ch = constString->charAt(static_cast<int32_t>(chIdx), vp->comp());
                    if (ch != 0) {
                        vp->replaceByConstant(node, TR::VPShortConst::create(vp, ch), isGlobal);
                        return node;
                    }
                }
            }
            TR::VPConstraint *arrayVPConstraint = vp->getConstraint(array, isGlobal);
            if (arrayVPConstraint) {
#ifdef J9_PROJECT_SPECIFIC
                TR::KnownObjectTable *knot = vp->comp()->getKnownObjectTable();
                TR::VPKnownObject *kobj = arrayVPConstraint->getKnownObject();
                if (knot && kobj) {
                    TR::KnownObjectTable::Index idx = kobj->getIndex();
                    if (kobj->isArrayWithConstantElements(vp->comp()) && kobj->isPrimitiveArray(vp->comp())) {
                        TR::VMAccessCriticalSection constrainAnyIntLoadCriticalSection(vp->comp(),
                            TR::VMAccessCriticalSection::tryToAcquireVMAccess);
                        if (constrainAnyIntLoadCriticalSection.hasVMAccess()) {
                            uintptr_t arrayObj = knot->getPointer(idx);
                            uintptr_t offset = vp->comp()->target().is64Bit() ? (uintptr_t)index->getUnsignedLongInt()
                                                                              : (uintptr_t)index->getUnsignedInt();
                            uintptr_t lengthInElements
                                = TR::Compiler->om.getArrayLengthInElements(vp->comp(), arrayObj);
                            TR::DataType elementType = kobj->getPrimitiveArrayDataType();
                            uintptr_t lengthInBytes = lengthInElements * TR::DataType::getSize(elementType);
                            uintptr_t validStart = TR::Compiler->om.contiguousArrayHeaderSizeInBytes();
                            uintptr_t validEnd = validStart + lengthInBytes;
                            uintptr_t accessSize = TR::DataType::getSize(dataType);
                            uintptr_t accessEnd = offset + accessSize;
                            if (validStart <= offset && offset < accessEnd && accessEnd <= validEnd) {
                                uintptr_t elementAddress
                                    = TR::Compiler->om.getAddressOfElement(vp->comp(), arrayObj, offset);
                                switch (dataType) {
                                    case TR::Int8: {
                                        uint8_t value = *((uint8_t *)(elementAddress));
                                        vp->replaceByConstant(node, TR::VPIntConst::create(vp, value), isGlobal);
                                        return node;
                                    }
                                    case TR::Int16: {
                                        uint16_t value = *((uint16_t *)(elementAddress));
                                        vp->replaceByConstant(node, TR::VPShortConst::create(vp, value), isGlobal);
                                        return node;
                                    }
                                    case TR::Int32: {
                                        uint32_t value = *((uint32_t *)(elementAddress));
                                        vp->replaceByConstant(node, TR::VPIntConst::create(vp, value), isGlobal);
                                        return node;
                                    }
                                    case TR::Int64: {
                                        uint64_t value = *((uint64_t *)(elementAddress));
                                        vp->replaceByConstant(node, TR::VPLongConst::create(vp, value), isGlobal);
                                        return node;
                                    }
                                }
                            }
                        }
                    }
                }
#endif
            }
        }
    }

    // avoid adding the constraint if the symbol represents a variant parm
    TR::Symbol *opSym = node->getOpCode().hasSymbolReference() ? node->getSymbolReference()->getSymbol() : NULL;
    if (opSym && (!opSym->isParm() || vp->isParmInvariant(opSym))) {
        // We can't constrain the 32-bit range based on the range of Int8 or
        // Int16, unless allowVPRangeNarrowingBasedOnDeclaredType.
        bool shouldConstrain = vp->comp()->getOption(TR_AllowVPRangeNarrowingBasedOnDeclaredType)
            || (dataType != TR::Int8 && dataType != TR::Int16);
        if (shouldConstrain) {
            TR::VPConstraint *constraint
                = TR::VPIntRange::createWithPrecision(vp, dataType, TR_MAX_DECIMAL_PRECISION, TR_maybe);
            if (constraint)
                vp->addGlobalConstraint(node, constraint);
        }
    }

    if (node->isNonNegative())
        vp->addBlockConstraint(node,
            TR::VPIntRange::create(vp, 0, static_cast<int32_t>(TR::getMaxSigned<TR::Int32>())));

    checkForNonNegativeAndOverflowProperties(vp, node);

    return node;
}

TR::Node *constrainShortLoad(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;

    constrainChildren(vp, node);

    TR::VPConstraint *constraint = TR::VPShortRange::createWithPrecision(vp, TR_MAX_DECIMAL_PRECISION);
    if (constraint)
        vp->addGlobalConstraint(node, constraint);

    if (node->isNonNegative())
        vp->addBlockConstraint(node,
            TR::VPShortRange::create(vp, 0, static_cast<int32_t>(TR::getMaxSigned<TR::Int16>())));

    checkForNonNegativeAndOverflowProperties(vp, node);

    vp->checkForInductionVariableLoad(node);

    return node;
}

TR::Node *constrainIntLoad(OMR::ValuePropagation *vp, TR::Node *node)
{
    bool wasReplacedByConstant = findConstant(vp, node);
    if (wasReplacedByConstant)
        return node;

    constrainChildren(vp, node);
    constrainAnyIntLoad(vp, node);

    vp->checkForInductionVariableLoad(node);

    if (node->getOpCode().isIndirect() && !vp->_curTree->getNode()->getOpCode().isNullCheck()
        && owningMethodDoesNotContainNullChecks(vp, node))
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));
    return node;
}

bool simplifyJ9ClassFlags(OMR::ValuePropagation *vp, TR::Node *node, bool isLong)
{
#ifdef J9_PROJECT_SPECIFIC
    uintptr_t cdfValue = 0;
    bool isGlobal;
    TR::VPConstraint *base = vp->getConstraint(node->getFirstChild(), isGlobal);
    TR::SymbolReference *symRef = node->getSymbolReference();

    if (symRef == vp->comp()->getSymRefTab()->findClassDepthAndFlagsSymbolRef() && base
        && base->isJ9ClassObject() == TR_yes && base->getClassType() && base->getClassType()->asFixedClass()) {
        cdfValue = vp->comp()->fej9()->getClassDepthAndFlagsValue(base->getClassType()->getClass());
        // if the type is a java.lang.Object, make the control branch to the call
        //
        if (base->getClassType()->asFixedClass()->isJavaLangObject(vp))
            cdfValue = TR::Compiler->cls.flagValueForFinalizerCheck(vp->comp());
    } else if (symRef == vp->comp()->getSymRefTab()->findClassFlagsSymbolRef()) {
        TR::VPConstraint *vft = base;
        if (node->getFirstChild()->getOpCode().isConversion())
            vft = vp->getConstraint(node->getFirstChild()->getFirstChild(), isGlobal);
        if (vft && vft->isFixedClass())
            cdfValue = TR::Compiler->cls.classFlagsValue(vft->getClass());
    }

    if (cdfValue > 0) {
        if (isLong)
            vp->replaceByConstant(node, TR::VPLongConst::create(vp, cdfValue), true);
        else
            vp->replaceByConstant(node, TR::VPIntConst::create(vp, cdfValue), true);
        return true;
    }
#endif
    return false;
}

// Also handles lloadi
//
TR::Node *constrainLload(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    if (node->getOpCode().isIndirect()) {
        if (refineUnsafeAccess(vp, node))
            return node;

        if (constrainCompileTimeLoad(vp, node))
            return node;
    }

    int64_t lo, hi;
    constrainRangeByPrecision(TR::getMinSigned<TR::Int64>(), TR::getMaxSigned<TR::Int64>(), TR_MAX_DECIMAL_PRECISION,
        lo, hi);
    TR::VPConstraint *constraint = TR::VPLongRange::create(vp, lo, hi);
    if (constraint)
        vp->addGlobalConstraint(node, constraint);

    if (node->isNonNegative())
        vp->addBlockConstraint(node, TR::VPLongRange::create(vp, 0, TR::getMaxSigned<TR::Int64>()));

    checkForNonNegativeAndOverflowProperties(vp, node);

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    vp->checkForInductionVariableLoad(node);

    if (node->getOpCodeValue() == TR::lloadi)
        simplifyJ9ClassFlags(vp, node, true);

    if (node->getOpCode().isIndirect() && !vp->_curTree->getNode()->getOpCode().isNullCheck()
        && owningMethodDoesNotContainNullChecks(vp, node))
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));
    return node;
}

// Also handles floadi
//
TR::Node *constrainFload(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (!findConstant(vp, node))
        constrainChildren(vp, node);

    if (node->getOpCode().isIndirect()) {
        if (refineUnsafeAccess(vp, node))
            return node;
    }

    if (node->getOpCode().isIndirect() && !vp->_curTree->getNode()->getOpCode().isNullCheck()
        && owningMethodDoesNotContainNullChecks(vp, node))
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));
    return node;
}

// Also handles dloadi
//
TR::Node *constrainDload(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (!findConstant(vp, node))
        constrainChildren(vp, node);

    if (node->getOpCode().isIndirect()) {
        if (refineUnsafeAccess(vp, node))
            return node;
    }

    if (node->getOpCode().isIndirect() && !vp->_curTree->getNode()->getOpCode().isNullCheck()
        && owningMethodDoesNotContainNullChecks(vp, node))
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));
    return node;
}

static const char *getFieldSignature(OMR::ValuePropagation *vp, TR::Node *node, int32_t &len)
{
    TR::SymbolReference *symRef = node->getSymbolReference();
    int32_t cookie = symRef->getCPIndex();
    const char *sig;

    if (cookie > 0)
        return symRef->getOwningMethod(vp->comp())->fieldSignatureChars(cookie, len);
    if (cookie == -1) {
        // Look for an array element reference. If this is such a reference, look
        // down to get the signature of the base and strip off one layer of array
        // indirection.
        //
        TR::Node *child = node->getFirstChild();
        if (child->isInternalPointer()) {
            child = child->getFirstChild();
            bool isGlobal;
            TR::VPConstraint *constraint = vp->getConstraint(child, isGlobal);
            if (constraint) {
                sig = constraint->getClassSignature(len);
                if (sig && *sig == '[') {
                    --len;
                    return sig + 1;
                }
            }
        }
    }
    return NULL;
}

/**
 * Try to add constraint to known objects or constant strings.
 * @parm vp The VP object.
 * @parm node The node whose value might be a known object or a constant string.
 * @parm isGlobal True to create a global constraint, or false for a block constraint.
 *
 * @return True if the constraint is created, otherwise false.
 */
static bool addKnownObjectConstraints(OMR::ValuePropagation *vp, TR::Node *node, bool isGlobal)
{
    // 'addKnownObjectConstraints` has a large overhead for remote compilations
    // due to the messages exchanged between client and server. However, constraints
    // are needed for VectorAPIExpansion optimization in OpenJ9 downstream project.
    if (vp->comp()->isOutOfProcessCompilation() && !vp->comp()->getOption(TR_EnableVectorAPIExpansion))
        return false;

    TR::SymbolReference *symRef = node->getSymbolReference();
    if (symRef->isUnresolved())
        return false;

    if (!vp->comp()->getKnownObjectTable())
        return false;

    uintptr_t *objectReferenceLocation = NULL;
    if (symRef->hasKnownObjectIndex()) {
        objectReferenceLocation = symRef->getKnownObjectReferenceLocation(vp->comp());
    }

#ifdef J9_PROJECT_SPECIFIC
    if (objectReferenceLocation) {
        TR_J9VMBase::ObjectClassInfo ci = vp->comp()->fej9()->getObjectClassInfoFromObjectReferenceLocation(vp->comp(),
            (uintptr_t)objectReferenceLocation);

        TR::VPConstraint *constraint = NULL;
        if (ci.isString && symRef->getSymbol()->isStatic()) {
            // There's a lot of machinery around optimizing const strings.  Even
            // though we lose object identity info this way, it's likely better to
            // engage the const string optimizations.
            //
            constraint = TR::VPClass::create(vp, TR::VPConstString::create(vp, symRef), TR::VPNonNullObject::create(vp),
                NULL, NULL, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::HeapObject));
        } else if (ci.jlClass) // without a jlClass, we can't tell what kind of constraint to add
        {
            const char *classSig = TR::Compiler->cls.classSignature(vp->comp(), ci.clazz, vp->trMemory());
            if (ci.isFixedJavaLangClass) {
                if (performTransformation(vp->comp(),
                        "%sAdd ClassObject constraint to %p based on known java/lang/Class %s =obj%d\n", OPT_DETAILS,
                        node, classSig, ci.knownObjectIndex)) {
                    constraint
                        = TR::VPClass::create(vp, TR::VPKnownObject::createForJavaLangClass(vp, ci.knownObjectIndex),
                            TR::VPNonNullObject::create(vp), NULL, NULL,
                            TR::VPObjectLocation::create(vp, TR::VPObjectLocation::JavaLangClassObject));
                }
            } else {
                if (performTransformation(vp->comp(),
                        "%sAdd known-object constraint to %p based on known object obj%d of class %s\n", OPT_DETAILS,
                        node, ci.knownObjectIndex, classSig)) {
                    constraint = TR::VPClass::create(vp, TR::VPKnownObject::create(vp, ci.knownObjectIndex),
                        TR::VPNonNullObject::create(vp), NULL, NULL,
                        TR::VPObjectLocation::create(vp, TR::VPObjectLocation::HeapObject));
                }
            }
        }

        if (constraint != NULL) {
            vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
            if (vp->trace()) {
                traceMsg(vp->comp(), "      -> Constraint is ");
                constraint->print(vp);
                traceMsg(vp->comp(), "\n");
            }
            return true;
        }
    }
#endif
    return false;
}

TR::Node *constrainAload(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;

    // before undertaking aload specific handling see if the default load transformation helps
    if (vp->transformDirectLoad(node)) {
        constrainNewlyFoldedConst(vp, node, false /* !isGlobal, conservative */);
        return node;
    }

    TR::VPConstraint *constraint = NULL;
#ifdef J9_PROJECT_SPECIFIC
    TR::SymbolReference *symRef = NULL;
    if (node->getOpCode().hasSymbolReference()) {
        symRef = node->getSymbolReference();

        if (symRef->getSymbol()->isLocalObject()) {
            vp->addGlobalConstraint(node, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::StackObject));
        }
        if (symRef->getSymbol()->isClassObject()) {
            vp->addGlobalConstraint(node, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject));
        }

        // Constraints based on known object info from the symref should be non-global.
        if (addKnownObjectConstraints(vp, node, false))
            return node;

        if (!symRef->getSymbol()->isArrayShadowSymbol()) {
            TR::Symbol *sym = symRef->getSymbol();
            bool allowForAOT = vp->comp()->getOption(TR_UseSymbolValidationManager);
            if (sym->isStatic() && !symRef->isUnresolved() && (sym->isPrivate() || sym->isFinal())) {
                TR_PersistentClassInfo *classInfo
                    = vp->comp()->getPersistentInfo()->getPersistentCHTable()->findClassInfoAfterLocking(
                        symRef->getOwningMethod(vp->comp())->classOfStatic(symRef->getCPIndex()), vp->comp(),
                        allowForAOT);
                if (classInfo && classInfo->getFieldInfo()) {
                    TR_PersistentFieldInfo *fieldInfo
                        = classInfo->getFieldInfo()->findFieldInfo(vp->comp(), node, false);
                    if (fieldInfo) {
                        TR_PersistentArrayFieldInfo *arrayFieldInfo
                            = fieldInfo ? fieldInfo->asPersistentArrayFieldInfo() : 0;
                        if (arrayFieldInfo && arrayFieldInfo->isDimensionInfoValid()) {
                            int32_t firstDimension = arrayFieldInfo->getDimensionInfo(0);
                            if (firstDimension >= 0) {
                                int32_t len;
                                const char *sig = getFieldSignature(vp, node, len);
                                if (sig && (len > 0) && (sig[0] == '[' || sig[0] == 'L')) {
                                    int32_t elementSize = arrayElementSize(sig, len, node, vp);
                                    if (elementSize != 0) {
                                        if (vp->trace())
                                            traceMsg(vp->comp(),
                                                "Using class lookahead info to find out non null, array dimension, and "
                                                "object location\n");
                                        vp->addGlobalConstraint(node,
                                            TR::VPArrayInfo::create(vp, firstDimension, firstDimension, elementSize));
                                        vp->addGlobalConstraint(node,
                                            TR::VPObjectLocation::create(vp, TR::VPObjectLocation::NotClassObject));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    int32_t len = 0;
    const char *sig;

    if (vp->comp()->getCurrentMethod()->isJ9() && symRef
        && (symRef == vp->comp()->getSymRefTab()->findOrCreateExcpSymbolRef() && vp->_curBlock->isCatchBlock()
            && vp->_curBlock->getExceptionClassNameChars())) {
        if (vp->_curBlock->getExceptionClass()) {
            constraint = NULL;
            if (vp->_curBlock->specializedDesyncCatchBlock()) {
                ListIterator<TR_Pair<TR::Node, TR::Block> > throwsIt(&vp->_predictedThrows);
                TR_Pair<TR::Node, TR::Block> *predictedThrow;
                for (predictedThrow = throwsIt.getFirst(); predictedThrow; predictedThrow = throwsIt.getNext()) {
                    TR::Node *predictedNode = predictedThrow->getKey();
                    TR::Block *predictedBlock = predictedThrow->getValue();

                    TR::TreeTop *tt;
                    TR::Node *node = vp->findThrowInBlock(predictedBlock, tt);

                    if (node != predictedNode)
                        continue;

                    if (node) {
                        // hack we're using the second child of the throw to store the destination block
                        // todo: make this less hacky
                        //
                        node->setNumChildren(2);
                        TR::Block *b = (TR::Block *)node->getSecondChild();
                        node->setNumChildren(1);
                        if (vp->_curBlock == b) {
                            constraint = TR::VPFixedClass::create(vp, vp->_curBlock->getExceptionClass());
                            break;
                        }
                    }
                }
            }
            if (!constraint)
                constraint = TR::VPResolvedClass::create(vp, vp->_curBlock->getExceptionClass());
        } else {
            len = vp->_curBlock->getExceptionClassNameLength();
            sig = TR::Compiler->cls.classNameToSignature(vp->_curBlock->getExceptionClassNameChars(), len, vp->comp());
            constraint = TR::VPUnresolvedClass::create(vp, sig, len, symRef->getOwningMethod(vp->comp()));
        }
        vp->addGlobalConstraint(node, constraint);
        vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
        vp->addGlobalConstraint(node, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::NotClassObject));
        node->setIsNonNull(true);
        return node;
    }

    bool isFixed = false;
    if (symRef && symRef->getSymbol()->isStatic()) {
        if (symRef->getSymbol()->isConstString() && !symRef->isUnresolved() && vp->comp()->getStringClassPointer()) {
            constraint = TR::VPClass::create(vp, TR::VPConstString::create(vp, symRef), TR::VPNonNullObject::create(vp),
                NULL, NULL, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::HeapObject));
            vp->addGlobalConstraint(node, constraint);
        } else {
            {
                sig = symRef->getTypeSignature(len, stackAlloc, &isFixed);
            }
            if (sig) {
                TR_ResolvedMethod *owningMethod = symRef->getOwningMethod(vp->comp());
                TR_OpaqueClassBlock *classBlock = vp->fe()->getClassFromSignature(sig, len, owningMethod);
                TR_OpaqueClassBlock *erased = NULL;
                if (!classBlock || vp->isUnreliableSignatureType(classBlock, erased)) {
                    classBlock = erased;

                    constraint = vp->createTypeHintConstraint(owningMethod, sig, len);
                    if (constraint) {
                        vp->addGlobalConstraint(node, constraint);
                    }
                }

                if (classBlock) {
                    TR_OpaqueClassBlock *jlClass = vp->fe()->getClassClassPointer(classBlock);
                    if (jlClass) {
                        if (classBlock != jlClass) {
                            isFixed = isFixed ? vp->canClassBeTrustedAsFixedClass(NULL, classBlock) : isFixed;
                            constraint = TR::VPClassType::create(vp, sig, len, owningMethod, isFixed, classBlock);
                            if (*sig == '[' || sig[0] == 'L') {
                                int32_t elementSize = arrayElementSize(sig, len, node, vp);
                                if (elementSize != 0) {
                                    constraint = TR::VPClass::create(vp, (TR::VPClassType *)constraint, NULL, NULL,
                                        TR::VPArrayInfo::create(vp, 0,
                                            TR::Compiler->om.maxArraySizeInElements(elementSize, vp->comp()),
                                            elementSize),
                                        TR::VPObjectLocation::create(vp, TR::VPObjectLocation::NotClassObject));
                                }
                            }
                        } else {
                            constraint = TR::VPObjectLocation::create(vp, TR::VPObjectLocation::JavaLangClassObject);
                        }
                        vp->addGlobalConstraint(node, constraint);
                    }
                } else {
                    TR_ResolvedMethod *method = node->getSymbolReference()->getOwningMethod(vp->comp());
                    constraint = TR::VPUnresolvedClass::create(vp, sig, len, method);
                    vp->addBlockConstraint(node, constraint);
                }
            }
        }
    }
#endif

    if (node->isNonNull())
        vp->addBlockConstraint(node, TR::VPNonNullObject::create(vp));
    else if (node->isNull())
        vp->addBlockConstraint(node, TR::VPNullObject::create(vp));

    bool isGlobal;
    constraint = vp->getConstraint(node, isGlobal);
    if (constraint && constraint->isNonNullObject())
        node->setIsNonNull(true);
    return node;
}

static TR::Node *findArrayLengthNode(OMR::ValuePropagation *vp, TR::Node *node, List<TR::Node> *arraylengthNodes)
{
    int32_t nodeVN = vp->getValueNumber(node);

    ListIterator<TR::Node> arraylengthNodesIt(arraylengthNodes);
    TR::Node *nextNode;
    for (nextNode = arraylengthNodesIt.getFirst(); nextNode != NULL; nextNode = arraylengthNodesIt.getNext()) {
        //
        // Check if the arraylength node is still in the trees and
        // still looks like an arraylength node
        //
        if (nextNode->getOpCode().isArrayLength() && (nextNode->getReferenceCount() > 0)) {
            if (nodeVN == vp->getValueNumber(nextNode->getFirstChild()))
                return nextNode;
        }
    }
    return NULL;
}

// For TR::aiadd and TR::aladd
TR::Node *constrainAddressRef(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    TR::Node *parent = vp->getCurrentParent();
    if (node->getFirstChild()->getOpCode().isLoadVar() && parent
        && ((parent->getOpCode().isLoadIndirect() || parent->getOpCode().isStoreIndirect())
            && (parent->getFirstChild() == node))) {
        // Possibly constraint indexNode with arraylength
    }

    return node;
}

TR::Node *constrainIloadi(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    if (refineUnsafeAccess(vp, node))
        return node;

    if (constrainCompileTimeLoad(vp, node))
        return node;

    TR::SymbolReference *symRef = node->getSymbolReference();
    bool isGlobal = false;
    TR::VPConstraint *base = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (base && base->isConstString()) {
        TR::VPConstString *kString = base->getClassType()->asConstString();
        int32_t *konst = 0;
        bool success = kString->getFieldByName(symRef, (void *&)konst, vp->comp());
        if (success) {
            int32_t realKonst;
            realKonst = *konst;
            if (!base->isNonNullObject() && vp->getCurrentParent()->getOpCodeValue() == TR::NULLCHK) {
                // replace node with a PassThrough in the original tree
                TR::Node *passThroughNode = TR::Node::create(TR::PassThrough, 1, node->getFirstChild());
                TR::Node *nullCheckNode = vp->getCurrentParent();
                nullCheckNode->setAndIncChild(0, passThroughNode);

                // create a treetop containing the iloadi following the current tree
                TR::TreeTop *newTree = TR::TreeTop::create(vp->comp(), TR::Node::create(TR::treetop, 1, node));
                node->decReferenceCount();
                TR::TreeTop *prevTree = vp->_curTree;
                newTree->join(prevTree->getNextTreeTop());
                prevTree->join(newTree);
            }
            vp->replaceByConstant(node, TR::VPIntConst::create(vp, realKonst), true);
            return node;
        }
    }

    // If the underlying object is a recognized object, we know that
    // its count field must be limited by the amount of heap memory that could
    // have been allocated for its buffer.
    //
#ifdef J9_PROJECT_SPECIFIC
    TR::Symbol::RecognizedField field = symRef->getSymbol()->getRecognizedField();

    if (field == TR::Symbol::Java_io_ByteArrayOutputStream_count) {
        // can't exceed address space in size
        vp->addGlobalConstraint(node, TR::VPIntRange::create(vp, 0, TR::getMaxSigned<TR::Int32>() >> 1));
        node->setIsNonNegative(true);
        node->setCannotOverflow(true);
    } else
#endif
        constrainAnyIntLoad(vp, node);

    if (simplifyJ9ClassFlags(vp, node, false))
        return node;

    if (!vp->_curTree->getNode()->getOpCode().isNullCheck() && owningMethodDoesNotContainNullChecks(vp, node))
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));
    return node;
}

TR::Node *constrainAloadi(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    if (refineUnsafeAccess(vp, node))
        return node;

    bool isGlobal;

    // an indirectly loaded object cannot be a stack object
    //
    vp->addGlobalConstraint(node, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::NotStackObject));

    TR::VPConstraint *constraint;
    int32_t len = 0;
    const char *sig = getFieldSignature(vp, node, len);

    if (node->getOpCode().hasSymbolReference()) {
        TR::Node *transformedNode = TR::TransformUtil::transformIndirectLoad(vp->comp(), node);
        if (transformedNode) {
            // Front-end has changed the node.
            //
            if (transformedNode != node) {
                vp->removeNode(node);
                transformedNode->incReferenceCount();
                node = transformedNode;
            }

            // If it is no longer an aloadi,
            // the rest of this handler may not be applicable anymore.
            //
            if (node->getOpCode().isLoadIndirect() && node->getDataType() == TR::Address) {
                // We can proceed
            } else {
                return node;
            }
        }

        // Constraints based on known object info from the symref should be non-global.
        if (addKnownObjectConstraints(vp, node, false))
            return node;

        TR::SymbolReference *symRef = node->getSymbolReference();
        bool attemptCompileTimeLoad = true;

#ifdef J9_PROJECT_SPECIFIC
        int32_t nonHelperId = symRef->getReferenceNumber() - vp->comp()->getSymRefTab()->getNumHelperSymbols();
        switch (nonHelperId) {
            case TR::SymbolReferenceTable::componentClassSymbol:
            case TR::SymbolReferenceTable::componentClassAsPrimitiveSymbol:
                // Don't try to dereference through <componentClass> unless we
                // observe below that the base address is constrained to a fixed
                // J9Class representing an array type. It's possible on dead paths
                // to have the base address point to a non-array J9Class, in which
                // case constrainCompileTimeLoad would load some garbage and treat
                // the garbage as a J9Class pointer for loadaddr.
                //
                // Note that if constrainCompileTimeLoad is called for an ancestor
                // node, it won't dereference through this <componentClass>,
                // instead expecting it to have a good constraint if possible.
                attemptCompileTimeLoad = false;
                break;
            default:
                break; // OK to attempt compile-time load
        }

        const static char *needInitializedCheck = feGetEnv("TR_needInitializedCheck");
        TR::VPConstraint *base = vp->getConstraint(node->getFirstChild(), isGlobal);

        if (base && node->getOpCode().hasSymbolReference()
            && (node->getSymbolReference() == vp->comp()->getSymRefTab()->findVftSymbolRef())) {
            TR_OpaqueClassBlock *clazz = NULL;
            TR::VPClassType *type = NULL;
            if (base->isClassObject() == TR_yes) {
                // base can only be an instance of java/lang/Class, since
                // we can't load <vft> relative to a J9Class.
                clazz = vp->comp()->getClassClassPointer();
                if (clazz != NULL)
                    type = TR::VPFixedClass::create(vp, clazz);
            } else if (base->isFixedClass()) {
                clazz = base->getClass();
                if (clazz != NULL)
                    type = TR::VPFixedClass::create(vp, clazz);
            } else if (base->getClass() && base->getClassType() && base->getClassType()->asResolvedClass()) {
                type = TR::VPResolvedClass::create(vp, base->getClass());
            }

            TR::VPClassPresence *nonnull = TR::VPNonNullObject::create(vp);
            TR::VPObjectLocation *loc = TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject);
            vp->addBlockOrGlobalConstraint(node, TR::VPClass::create(vp, type, nonnull, NULL, NULL, loc), isGlobal);

            node->setIsNonNull(true);
        } else if (base && base->getKnownObject() && !symRef->isUnresolved()
            && symRef->getSymbol()->isRecognizedKnownObjectShadow() && !vp->comp()->compileRelocatableCode())
        // Base is known object, and field is recognized known object shadow,
        // hence can be treated as known object.
        {
            TR::VMAccessCriticalSection constrainAloadiCriticalSection(vp->comp(),
                TR::VMAccessCriticalSection::tryToAcquireVMAccess);
            if (constrainAloadiCriticalSection.hasVMAccess()) {
                TR::KnownObjectTable *knot = vp->comp()->getOrCreateKnownObjectTable();
                uintptr_t baseObject = knot->getPointer(base->getKnownObject()->getIndex());
                if (baseObject) {
                    // Check if baseObject is actually the recognized field Class
                    TR_OpaqueClassBlock *baseObjectClazz = TR::Compiler->cls.objectClass(vp->comp(), baseObject);
                    int32_t recognizedClassNameLength;
                    const char *recognizedClassName
                        = symRef->getSymbol()->owningClassNameCharsForRecognizedField(recognizedClassNameLength);
                    if (recognizedClassName) {
                        TR_OpaqueClassBlock *recognizedClazz = vp->fe()->getClassFromSignature(recognizedClassName,
                            recognizedClassNameLength, symRef->getOwningMethod(vp->comp()));
                        if (recognizedClazz
                            && (TR_yes == vp->fe()->isInstanceOf(baseObjectClazz, recognizedClazz, false))) {
                            uintptr_t fieldObject = vp->comp()->fej9()->getReferenceFieldAtAddress(
                                baseObject + node->getSymbolReference()->getOffset());
                            if (0 != fieldObject) {
                                TR_OpaqueClassBlock *fieldObjectClazz
                                    = TR::Compiler->cls.objectClass(vp->comp(), fieldObject);
                                if (!TR::Compiler->cls.isClassArray(vp->comp(), fieldObjectClazz)) {
                                    traceMsg(vp->comp(), "Recognized known object field node %p \n", node);
                                    TR::KnownObjectTable::Index fieldObjectKnotIndex
                                        = knot->getOrCreateIndex(fieldObject);
                                    // Global constraints should work here, as field loads get fresh value numbers
                                    constraint
                                        = TR::VPClass::create(vp, TR::VPKnownObject::create(vp, fieldObjectKnotIndex),
                                            TR::VPNonNullObject::create(vp), NULL, NULL,
                                            TR::VPObjectLocation::create(vp, TR::VPObjectLocation::HeapObject));
                                    vp->addGlobalConstraint(node, constraint);
                                } else {
                                    int32_t arrLength
                                        = TR::Compiler->om.getArrayLengthInElements(vp->comp(), fieldObject);
                                    traceMsg(vp->comp(), "Recognized known array field node %p length %d\n", node,
                                        arrLength);
                                    // Global constraints should work here, as field loads get fresh value numbers
                                    vp->addGlobalConstraint(node, TR::VPFixedClass::create(vp, fieldObjectClazz));
                                    vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
                                    vp->addGlobalConstraint(node,
                                        TR::VPArrayInfo::create(vp, arrLength, arrLength,
                                            TR::Compiler->om.getArrayElementWidthInBytes(vp->comp(), fieldObject)));
                                }
                            }
                        }
                    }
                }
            }
        } else if (base && base->isJavaLangClassObject() == TR_yes && base->isNonNullObject()
            && base->getKnownObject()) {
            if (symRef == vp->comp()->getSymRefTab()->findClassFromJavaLangClassSymbolRef()) {
                TR::KnownObjectTable *knot = vp->comp()->getOrCreateKnownObjectTable();
                TR_ASSERT(knot, "Can not have a TR::VPKnownObject without a known-object table");

                {
                    TR::VMAccessCriticalSection constrainAloadiCriticalSection(vp->comp(),
                        TR::VMAccessCriticalSection::tryToAcquireVMAccess);

                    if (constrainAloadiCriticalSection.hasVMAccess()) {
                        uintptr_t jlclazz = knot->getPointer(base->getKnownObject()->getIndex());
                        TR_OpaqueClassBlock *clazz = TR::Compiler->cls.classFromJavaLangClass(vp->comp(), jlclazz);
                        if (TR::Compiler->cls.isClassInitialized(vp->comp(), clazz)) {
                            // this should be able to be precise because we do know the compenent class pointer
                            // precisely but clearly others make invalid assumptions on seeing this fixed class
                            // constraint so be conservative and don't let the world know if we aren't sure the elements
                            // match this type
                            if (!TR::Compiler->cls.isInterfaceClass(vp->comp(), clazz)
                                && (TR::Compiler->cls.isClassFinal(vp->comp(), clazz)
                                    || TR::Compiler->cls.isPrimitiveClass(vp->comp(), clazz))) {
                                vp->addBlockOrGlobalConstraint(node,
                                    TR::VPClass::create(vp, TR::VPFixedClass::create(vp, clazz),
                                        TR::VPNonNullObject::create(vp), NULL, NULL,
                                        TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject)),
                                    isGlobal);
                            } else {
                                vp->addBlockOrGlobalConstraint(node,
                                    TR::VPClass::create(vp, TR::VPResolvedClass::create(vp, clazz),
                                        TR::VPNonNullObject::create(vp), NULL, NULL,
                                        TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject)),
                                    isGlobal);
                            }
                            node->setIsNonNull(true);
                        }
                    }

                } // VM Access Critical Section
            }
        } else if (base && base->isJ9ClassObject() == TR_yes && base->isNonNullObject() && base->getClassType()
            && base->getClassType()->asFixedClass() && base->getClass()
            && (!needInitializedCheck || TR::Compiler->cls.isClassInitialized(vp->comp(), base->getClass()))) {
            TR::KnownObjectTable *knot = vp->comp()->getOrCreateKnownObjectTable();
            if (knot && symRef == vp->comp()->getSymRefTab()->findJavaLangClassFromClassSymbolRef()) {
                TR_J9VMBase *fej9 = (TR_J9VMBase *)(vp->comp()->fe());
                TR::KnownObjectTable::Index knownObjectIndex = knot->getOrCreateIndexAt(
                    (uintptr_t *)(base->getClass() + fej9->getOffsetOfJavaLangClassFromClassField()));
                vp->addBlockOrGlobalConstraint(node,
                    TR::VPClass::create(vp, TR::VPKnownObject::createForJavaLangClass(vp, knownObjectIndex),
                        TR::VPNonNullObject::create(vp), NULL, NULL,
                        TR::VPObjectLocation::create(vp, TR::VPObjectLocation::JavaLangClassObject)),
                    isGlobal);
                node->setIsNonNull(true);
            } else if (symRef == vp->comp()->getSymRefTab()->findArrayComponentTypeSymbolRef()
                && base->getClassType()->isArray() == TR_yes) {
                attemptCompileTimeLoad = true;
                TR_OpaqueClassBlock *componentClass
                    = vp->comp()->fej9()->getComponentClassFromArrayClass(base->getClass());
                // this should be able to be precise because we do know the compenent class pointer precisely
                // but clearly others make invalid assumptions on seeing this fixed class constraint so be conservative
                // and don't let the world know if we aren't sure the elements match this type
                if (!TR::Compiler->cls.isInterfaceClass(vp->comp(), componentClass)
                    && (TR::Compiler->cls.isClassFinal(vp->comp(), componentClass)
                        || TR::Compiler->cls.isPrimitiveClass(vp->comp(), componentClass))) {
                    vp->addBlockOrGlobalConstraint(node,
                        TR::VPClass::create(vp, TR::VPFixedClass::create(vp, componentClass),
                            TR::VPNonNullObject::create(vp), NULL, NULL,
                            TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject)),
                        isGlobal);
                } else {
                    vp->addBlockOrGlobalConstraint(node,
                        TR::VPClass::create(vp, TR::VPResolvedClass::create(vp, componentClass),
                            TR::VPNonNullObject::create(vp), NULL, NULL,
                            TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject)),
                        isGlobal);
                }
                node->setIsNonNull(true);
            }
        } else if (symRef == vp->comp()->getSymRefTab()->findJavaLangClassFromClassSymbolRef()) {
            vp->addGlobalConstraint(node, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::JavaLangClassObject));
            vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
            node->setIsNonNull(true);
        } else if (symRef == vp->comp()->getSymRefTab()->findClassFromJavaLangClassSymbolRef()) {
            vp->addGlobalConstraint(node, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject));
            vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
            node->setIsNonNull(true);
        }

#endif

        if (attemptCompileTimeLoad && constrainCompileTimeLoad(vp, node))
            return node;

#ifdef J9_PROJECT_SPECIFIC
        TR::Symbol *sym = node->getSymbol();
        switch (sym->getRecognizedField()) {
            case TR::Symbol::Java_lang_String_value:
            case TR::Symbol::Java_lang_invoke_MethodHandle_thunks:
                if (!node->isNonNull()
                    && performTransformation(vp->comp(), "%s[%p] recognized field is never null: %s\n", OPT_DETAILS,
                        node, symRef->getName(vp->comp()->getDebug()))) {
                    vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
                    node->setIsNonNull(true);
                }
                break;
            case TR::Symbol::Java_lang_invoke_MutableCallSite_target: {
                if (!vp->comp()->getOption(TR_DisableMCSBypass) && vp->comp()->getMethodHotness() >= hot) {
                    TR::VPConstraint *callSiteConstraint = vp->getConstraint(node->getFirstChild(), isGlobal);
                    if (callSiteConstraint && callSiteConstraint->getKnownObject()) {
                        // Note that the known object constraint does not preclude
                        // the possibility that the callSite expression is null at
                        // this location at run time.  However, we can still do the
                        // optimization on the premise that the code must contain
                        // a null check before this code runs, or else it's already
                        // wrong.  (This is an example of an opt that benefits from
                        // the fact that known object constraints are conditional
                        // on the expression being non-null.)
                        //
                        static char *bypassOnEveryVPRun = feGetEnv("TR_bypassOnEveryVPRun");
                        bool bypassOnThisRun = bypassOnEveryVPRun || vp->getLastRun();
                        if (bypassOnThisRun) {
                            TR::KnownObjectTable::Index callSiteKOI = callSiteConstraint->getKnownObject()->getIndex();
                            uintptr_t *bypassLocation = NULL;

                            {
                                TR::VMAccessCriticalSection constrainAloadiCriticalSection(vp->comp(),
                                    TR::VMAccessCriticalSection::tryToAcquireVMAccess);
                                if (constrainAloadiCriticalSection.hasVMAccess()) {
                                    uintptr_t *siteLocation
                                        = vp->comp()->getKnownObjectTable()->getPointerLocation(callSiteKOI);
                                    bypassLocation = vp->comp()->fej9()->mutableCallSite_bypassLocation(*siteLocation);
                                    if (!bypassLocation
                                        && performTransformation(vp->comp(),
                                            "%s[%p] create CallSite bypass for obj%d\n", OPT_DETAILS, node,
                                            callSiteKOI)) {
                                        bypassLocation = vp->comp()->fej9()->mutableCallSite_findOrCreateBypassLocation(
                                            *siteLocation);
                                    }
                                } else {
                                    if (vp->trace())
                                        traceMsg(vp->comp(),
                                            "[%p] Unable to acquire VM access.  Not attempting to bypass CallSite\n",
                                            node);
                                }
                            }

                            if (bypassLocation
                                && performTransformation(vp->comp(), "%s[%p] bypass CallSite load from obj%d\n",
                                    OPT_DETAILS, node, callSiteKOI)) {
                                TR::VPConstraint *targetConstraint = vp->getConstraint(node, isGlobal);
                                TR::KnownObjectTable::Index targetKOI = TR::KnownObjectTable::UNKNOWN;
                                if (targetConstraint && targetConstraint->getKnownObject())
                                    targetKOI = targetConstraint->getKnownObject()->getIndex();
                                vp->removeChildren(node);
                                TR::Node::recreate(node, TR::aload);
                                node->setNumChildren(0);
                                node->setSymbolReference(
                                    vp->comp()->getSymRefTab()->createKnownStaticReferenceSymbolRef(bypassLocation,
                                        targetKOI));
                                return node;
                            }
                        }
                    }
                }
            } break;
            default:
                break;
        }

        if (!symRef->getSymbol()->isArrayShadowSymbol()) {
            TR::Node *underlyingObject = node->getFirstChild();
            constraint = vp->getConstraint(underlyingObject, isGlobal);
            if (constraint && constraint->getClass()) {
                int32_t len;
                const char *objectSig = constraint->getClassSignature(len);
                if (objectSig && (len == 19) && constraint->isFixedClass()
                    && !strncmp(objectSig, "Ljava/util/TreeMap;", 19)) {
                    TR_ResolvedMethod *method = node->getSymbolReference()->getOwningMethod(vp->comp());
                    TR_OpaqueClassBlock *clazz = vp->fe()->getClassFromSignature(objectSig, len, method);
                    if (clazz) {
                        TR_ASSERT(!TR::Compiler->cls.isInterfaceClass(vp->comp(), clazz),
                            "java/util/TreeMap is not an interface");
                        TR_OpaqueClassBlock *jlClass = vp->fe()->getClassClassPointer(clazz);
                        if (jlClass) {
                            if (clazz != jlClass) {
                                int32_t fieldNameLen = -1;
                                char *fieldName = NULL;
                                if (node && node->getOpCode().hasSymbolReference()
                                    && node->getSymbolReference()->getSymbol()->isShadow()) {
                                    fieldName = node->getSymbolReference()
                                                    ->getOwningMethod(vp->comp())
                                                    ->fieldName(node->getSymbolReference()->getCPIndex(), fieldNameLen,
                                                        vp->comp()->trMemory());
                                }

                                if (fieldName && (fieldNameLen > 0)
                                    && !strncmp(fieldName, "java/util/TreeMap.valuesCollection", 34)) {
                                    TR_OpaqueClassBlock *clazz
                                        = vp->fe()->getClassFromSignature("Ljava/util/TreeMap$2;", 21, method);
                                    if (clazz != NULL) {
                                        constraint = TR::VPFixedClass::create(vp, clazz);
                                        vp->addGlobalConstraint(node, constraint);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
#endif
    }

#ifdef J9_PROJECT_SPECIFIC
    // don't use getClassFromSignature for arrays to determine the type of the
    // component as its done below later using the right api
    //
    TR::Symbol *sym = node->getSymbol();
    TR_OpaqueClassBlock *declaredClass = sym->getDeclaredClass();
    if ((declaredClass != NULL || sig != NULL)
        && !(node->getOpCode().hasSymbolReference() && node->getSymbol()->isArrayShadowSymbol()
            && node->getFirstChild()->getOpCode().isArrayRef())) {
        TR_ResolvedMethod *method = node->getSymbolReference()->getOwningMethod(vp->comp());
        TR::Symbol *sym = node->getSymbol();
        TR_OpaqueClassBlock *classBlock = declaredClass;
        if (classBlock == NULL)
            classBlock = vp->fe()->getClassFromSignature(sig, len, method);

        TR_OpaqueClassBlock *erased = NULL;
        if (!classBlock || vp->isUnreliableSignatureType(classBlock, erased)) {
            classBlock = erased;

            constraint = vp->createTypeHintConstraint(method, sig, len);
            if (constraint) {
                vp->addGlobalConstraint(node, constraint);
            }
        }

        if (classBlock) {
            TR_OpaqueClassBlock *jlClass = vp->fe()->getClassClassPointer(classBlock);
            if (jlClass) {
                if (classBlock != jlClass) {
                    constraint = TR::VPResolvedClass::create(vp, classBlock);
                    if (TR::Compiler->cls.isClassArray(vp->comp(), classBlock)) {
                        int32_t elementSize = TR::Compiler->cls.getArrayElementWidthInBytes(vp->comp(), classBlock);

                        constraint = TR::VPClass::create(vp, (TR::VPClassType *)constraint, NULL, NULL,
                            TR::VPArrayInfo::create(vp, 0,
                                TR::Compiler->om.maxArraySizeInElements(elementSize, vp->comp()), elementSize),
                            TR::VPObjectLocation::create(vp, TR::VPObjectLocation::NotClassObject));
                    }
                } else
                    constraint = TR::VPObjectLocation::create(vp, TR::VPObjectLocation::JavaLangClassObject);
                vp->addGlobalConstraint(node, constraint);
            }
        }
    }

    if (node->getOpCode().hasSymbolReference() && node->getSymbol()->isArrayShadowSymbol()
        && node->getFirstChild()->getOpCode().isArrayRef()) {
        TR::Node *underlyingArray = node->getFirstChild()->getFirstChild();
        if (vp->comp()->generateArraylets() && underlyingArray && underlyingArray->getOpCode().hasSymbolReference()
            && underlyingArray->getSymbol()->isArrayletShadowSymbol()
            && underlyingArray->getFirstChild()->getOpCode().isArrayRef()) {
            underlyingArray = underlyingArray->getFirstChild()->getFirstChild();
        }

        constraint = vp->getConstraint(underlyingArray, isGlobal);
        if (constraint && constraint->getClass()) {
            int32_t len;
            const char *arraySig = constraint->getClassSignature(len);
            if (arraySig && *arraySig == '[') {
                TR_OpaqueClassBlock *clazz
                    = vp->comp()->fej9()->getComponentClassFromArrayClass(constraint->getClass());

                if (vp->comp()->getMethodHotness() >= scorching) {
                    int32_t prefetchOffset = vp->comp()->fej9()->findFirstHotFieldTenuredClassOffset(vp->comp(), clazz);

                    if (prefetchOffset >= 0) {
                        if (vp->comp()->findPrefetchInfo(node) < 0) {
                            TR_Pair<TR::Node, uint32_t> *prefetchInfo = new (vp->comp()->trHeapMemory())
                                TR_Pair<TR::Node, uint32_t>(node, (uint32_t *)(intptr_t)prefetchOffset);
                            vp->comp()->getNodesThatShouldPrefetchOffset().push_front(prefetchInfo);
                        }
                    }
                }

                /*
                We removed the check "!TR::Compiler->cls.isInterfaceClass(vp->comp(), clazz)"
                since java treats classes and interfaces in the same way
                if they are array elements.
                For example, the following two snippets are both rejected by javac but accepted by a bytecode verifier.
                Both throw an ArrayStoreException at runtime.

                Serializable [] sArray = new Serializable[1];
                sArray = new Object();

                StringBuilder [] sArray = new StringBuilder[1];
                sArray = new String();

                However, if we change sArray to a static
                this line staticField = new Object(); won't produce any exceptions if staticField is Serializable
                but it will be rejected by the bytecode verifier if staticField is of a class type.
                */
                if (clazz) {
                    TR_OpaqueClassBlock *jlClass = vp->comp()->fej9()->getClassClassPointer(clazz);
                    if (jlClass) {
                        if (clazz != jlClass) {
                            int32_t fieldNameLen = -1;
                            char *fieldName = NULL;
                            if (underlyingArray && underlyingArray->getOpCode().hasSymbolReference()
                                && (underlyingArray->getSymbolReference()->getSymbol()->isStaticField()
                                    || underlyingArray->getSymbolReference()->getSymbol()->isShadow())) {
                                if (underlyingArray->getSymbolReference()->getSymbol()->isShadow())
                                    fieldName = underlyingArray->getSymbolReference()
                                                    ->getOwningMethod(vp->comp())
                                                    ->fieldName(underlyingArray->getSymbolReference()->getCPIndex(),
                                                        fieldNameLen, vp->comp()->trMemory());
                                else
                                    fieldName = underlyingArray->getSymbolReference()
                                                    ->getOwningMethod(vp->comp())
                                                    ->staticName(underlyingArray->getSymbolReference()->getCPIndex(),
                                                        fieldNameLen, vp->comp()->trMemory());
                            }

                            if (fieldName && (fieldNameLen > 0)
                                && (!strncmp(fieldName, "java/math/BigDecimal.CACHE0", 27)
                                    || !strncmp(fieldName, "java/math/BigDecimal.CACHE1", 27)
                                    || !strncmp(fieldName, "java/math/BigDecimal.CACHE2", 27)))
                                constraint = TR::VPFixedClass::create(vp, clazz);
                            else
                                constraint = TR::VPResolvedClass::create(vp, clazz);
                        } else
                            constraint = TR::VPObjectLocation::create(vp, TR::VPObjectLocation::JavaLangClassObject);
                        vp->addGlobalConstraint(node, constraint);
                    }
                }
            }
        }
    }

    TR::VPConstraint *base = vp->getConstraint(node->getFirstChild(), isGlobal);

    if (base && base->getClass() && !vp->comp()->getOption(TR_DisableMarkingOfHotFields)
        && node->getFirstChild()->getOpCode().hasSymbolReference()
        && node->getFirstChild()->getSymbol()->isCollectedReference()
        && !vp->comp()->fej9()->isHotReferenceFieldRequired()
        && vp->_curBlock->getGlobalNormalizedFrequency(vp->comp()->getFlowGraph()) >= TR::Options::_hotFieldThreshold) {
        vp->comp()->fej9()->markHotField(vp->comp(), node->getSymbolReference(), base->getClass(),
            base->isFixedClass());
    }

    if (node->getSymbolReference()) {
        if (!node->getSymbolReference()->isUnresolved()
            && (node->getSymbolReference()->getSymbol()->getKind() == TR::Symbol::IsShadow)
            && (node->getSymbolReference()->getCPIndex() >= 0)
            && node->getSymbolReference()->getOwningMethod(vp->comp())) {
            int32_t fieldSigLen;
            const char *fieldSig = node->getSymbolReference()
                                       ->getOwningMethod(vp->comp())
                                       ->fieldSignatureChars(node->getSymbolReference()->getCPIndex(), fieldSigLen);
            int32_t fieldNameLen;
            const char *fieldName = node->getSymbolReference()
                                        ->getOwningMethod(vp->comp())
                                        ->fieldNameChars(node->getSymbolReference()->getCPIndex(), fieldNameLen);
            char *className = node->getSymbolReference()->getOwningMethod(vp->comp())->classNameChars();
            uint16_t classNameLen = node->getSymbolReference()->getOwningMethod(vp->comp())->classNameLength();
            if (fieldName && !strncmp(fieldName, "this$0", 6) && className && !strncmp(className, "java/util/", 10)) {
                if (vp->trace())
                    traceMsg(vp->comp(), "NonNull node %d className %.*s fieldSig %.*s fieldName %.*s\n",
                        node->getGlobalIndex(), classNameLen, className, fieldSigLen, fieldSig, fieldNameLen,
                        fieldName);
                vp->addBlockConstraint(node, TR::VPNonNullObject::create(vp));
            }
        }
    }
#endif

    if (node->isNonNull())
        vp->addBlockConstraint(node, TR::VPNonNullObject::create(vp));
    else if (node->isNull())
        vp->addBlockConstraint(node, TR::VPNullObject::create(vp));

    constraint = vp->getConstraint(node, isGlobal);
    if (!vp->_curTree->getNode()->getOpCode().isNullCheck() && owningMethodDoesNotContainNullChecks(vp, node))
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));

    return node;
}

TR::Node *constrainStore(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    // storage access here, sync region ends
    // memory barrier required at next critical region
    bool globalStore = false;
    if (!node->getSymbol()->isAutoOrParm()) {
        globalStore = true;

        if (node->getOpCode().isStore()) {
            if (node->getSymbolReference() == vp->comp()->getSymRefTab()->findThisRangeExtensionSymRef())
                globalStore = false;
        }
    }

    if (globalStore) {
        OMR::ValuePropagation::Relationship *syncRel = vp->findConstraint(vp->_syncValueNumber);
        TR::VPSync *sync = NULL;
        if (syncRel && syncRel->constraint)
            sync = syncRel->constraint->asVPSync();
        if (sync && sync->syncEmitted() == TR_yes) {
            vp->addConstraintToList(NULL, vp->_syncValueNumber, vp->AbsoluteConstraint,
                TR::VPSync::create(vp, TR_maybe), &vp->_curConstraints);
            if (vp->trace()) {
                traceMsg(vp->comp(), "Setting syncRequired due to node [%p]\n", node);
            }
        } else {
            if (vp->trace()) {
                if (sync)
                    traceMsg(vp->comp(), "syncRequired is already setup at node [%p]\n", node);
                else
                    traceMsg(vp->comp(), "No sync constraint found at node [%p]!\n", node);
            }
        }
    }

    if (refineUnsafeAccess(vp, node) || (node->getSymbol()->isAutoOrParm() && node->storedValueIsIrrelevant()))
        return node;

    // If the child is not the original child, inject an equality constraint
    // between this node and the child.
    //
    TR::Node *valueChild;
    if (node->getOpCode().isIndirect())
        valueChild = node->getSecondChild();
    else
        valueChild = node->getFirstChild();

#ifdef J9_PROJECT_SPECIFIC
    if (valueChild->getType().isBCD()) // sign state constraints are currently only propagated to loads so only
                                       // constrain on stores vs every BCD node
        valueChild = constrainBCDSign(vp, valueChild);
#endif

    if (vp->getValueNumber(node) != vp->getValueNumber(valueChild))
        vp->addBlockConstraint(node, TR::VPEqual::create(vp, 0), valueChild);
    if (node->getOpCode().isIndirect() && !vp->_curTree->getNode()->getOpCode().isNullCheck()
        && owningMethodDoesNotContainNullChecks(vp, node))
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));

    if (!node->getOpCode().isIndirect() && vp->_curDefinedOnAllPaths && node->getSymbol()->isAutoOrParm()) {
        vp->_curDefinedOnAllPaths->set(node->getSymbolReference()->getReferenceNumber());
    }

    return node;
}

// Handles istore, bstore, sstore
//
TR::Node *constrainIntStore(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainStore(vp, node);

    // See if this is the store that is incrementing an induction variable
    //
    vp->checkForInductionVariableIncrement(node);

    // See if the store is a local boolean negation that can be eliminated.
    // If so, don't treat it as a store.
    //
    TR::Node *child = node->getFirstChild();
    TR::Symbol *storeSymbol = node->getSymbol();

    if (child->getOpCodeValue() == TR::ixor && child->getSecondChild()->getOpCodeValue() == TR::iconst
        && child->getSecondChild()->getInt() == 1) {
        TR::Node *loadNode = child->getFirstChild();
        if (loadNode->getOpCode().isLoadVarDirect() && loadNode->getSymbol() == storeSymbol) {
            // This is a boolean negate.
            //
            // See if there is an active boolean negation in the current block
            // whose store is the same value as the load of this negation.
            // If so, this negation can be replaced by the first one's load node
            //
            int32_t loadVN = vp->getValueNumber(loadNode);
            OMR::ValuePropagation::BooleanNegationInfo *bni;
            for (bni = vp->_booleanNegationInfo.getFirst(); bni; bni = bni->getNext()) {
                if (bni->_storeValueNumber == loadVN) {
                    // Found a matching boolean negation
                    //
                    if (performTransformation(vp->comp(), "%sRemoving double boolean negation at [%p]\n", OPT_DETAILS,
                            node)) {
                        bni->_loadNode->incReferenceCount();
                        vp->removeChildren(node, true);
                        node->setFirst(bni->_loadNode);
                        node->setNumChildren(1);
                        vp->addBlockConstraint(node, TR::VPEqual::create(vp, 0), bni->_loadNode);
                        return node;
                    }
                }
            }

            // Mark this node as an active boolean negation
            //
            bni = new (vp->trStackMemory()) OMR::ValuePropagation::BooleanNegationInfo;
            bni->_storeValueNumber = vp->getValueNumber(node);
            bni->_loadNode = loadNode;
            vp->_booleanNegationInfo.add(bni);
        }
    }
    return node;
}

TR::Node *constrainLongStore(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainStore(vp, node);

    // See if this is the store that is incrementing an induction variable
    //
    vp->checkForInductionVariableIncrement(node);
    return node;
}

// Also handles astorei
//
TR::Node *constrainAstore(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainStore(vp, node);

    // See if there is a constraint for this value
    //
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (constraint) {
        if (constraint->isNullObject())
            node->setIsNull(true);
        else if (constraint->isNonNullObject())
            node->setIsNonNull(true);

        // for localVP, check if the value being stored is
        // consistent with the info on the parm
        //
        vp->invalidateParmConstraintsIfNeeded(node, constraint);
    }
    return node;
}

void canRemoveWrtBar(OMR::ValuePropagation *vp, TR::Node *node)
{
    // If the value being stored is known to be null we can replace the write
    // barrier store with a regular store
    //
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node, isGlobal);
    if (constraint) {
        if (constraint->isNullObject() && TR::Compiler->om.writeBarrierType() != gc_modron_wrtbar_always
            && !vp->comp()->getOptions()->realTimeGC()) {
            if (node->getOpCode().isIndirect()) {
                if (performTransformation(vp->comp(), "%sChanging write barrier store into astorei [%p]\n", OPT_DETAILS,
                        node)) {
                    bool invalidateInfo = false;
                    if (node->getChild(2) != node->getFirstChild())
                        invalidateInfo = true;

                    TR::Node::recreate(node, TR::astorei);
                    node->getChild(2)->recursivelyDecReferenceCount();
                    node->setNumChildren(2);
                    node->setIsNull(true);
                    if (invalidateInfo) {
                        vp->invalidateUseDefInfo();
                        vp->invalidateValueNumberInfo();
                    }
                }
            } else {
                if (performTransformation(vp->comp(), "%sChanging write barrier store into astore [%p]\n", OPT_DETAILS,
                        node)) {
                    TR::Node::recreate(node, TR::astore);
                    node->getChild(1)->recursivelyDecReferenceCount();
                    node->setNumChildren(1);
                    node->setIsNull(true);
                    vp->invalidateUseDefInfo();
                    vp->invalidateValueNumberInfo();
                }
            }
        } else if (constraint->isNonNullObject())
            node->setIsNonNull(true);
    }
}

// Also handles iwrtbar
//
TR::Node *constrainWrtBar(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    if (node->getOpCode().isIndirect()) {
        if (refineUnsafeAccess(vp, node))
            return node;
    }

    // The case of ArrayStoreCHK will be taken care in constrainArrayStoreChk().
    //
    if (!vp->getCurrentParent()
        || (vp->getCurrentParent() && vp->getCurrentParent()->getOpCodeValue() != TR::ArrayStoreCHK))
        canRemoveWrtBar(vp, node);

    static bool doOpt = feGetEnv("TR_DisableWrtBarOpt") ? false : true;

    if (TR::Compiler->om.readBarrierType() != gc_modron_readbar_none) {
        // The optimization below targets the following type of code:
        //
        //     X x = new X();
        //     x.a = new A();
        //
        // Most of the time both newly allocated objects would be in the nursery and there is no need for a generational
        // barrier. However, in a rare case that a GC occurs in between the two allocations above the premise can be
        // violated (by tenuring the first, but not the second object). The GC will compensate for the lack of the
        // barrier by auto-remembering all objects that are tenured and directly stack referenced.
        //
        // This optimization is currently disabled when read barriers are required as it is currently unknown whether
        // this optimization is safe under such a GC mode.
        doOpt = false;
    }

    auto gcMode = TR::Compiler->om.writeBarrierType();

    if (doOpt && ((gcMode == gc_modron_wrtbar_cardmark_and_oldcheck) || (gcMode == gc_modron_wrtbar_oldcheck))
        && (node->getOpCodeValue() == TR::awrtbari) && !node->skipWrtBar()) {
        TR::Node *valueChild = node->getFirstChild();
        if (valueChild->getOpCode().isArrayRef())
            valueChild = valueChild->getFirstChild();

        bool removeWrtBar = false;

        if ((valueChild->getOpCodeValue() == TR::New) || (valueChild->getOpCodeValue() == TR::newarray)
            || (valueChild->getOpCodeValue() == TR::anewarray) || (valueChild->getOpCodeValue() == TR::multianewarray)
            || (valueChild && (valueChild->getOpCodeValue() == TR::loadaddr)
                && valueChild->getSymbolReference()->getSymbol()->isAuto()
                && valueChild->getSymbolReference()->getSymbol()->isLocalObject()))
            removeWrtBar = true;
        else if (valueChild->getOpCodeValue() == TR::aload) {
            TR_UseDefInfo *useDefInfo = vp->_useDefInfo;

            if (valueChild->isThisPointer()
                && vp->comp()->getJittedMethodSymbol()->getResolvedMethod()->isConstructor()) {
                removeWrtBar = true;

                if (vp->_changedThis == TR_maybe) {
                    vp->_changedThis = TR_no;
                    TR::TreeTop *tt = vp->comp()->getStartTree();
                    for (; tt; tt = tt->getNextTreeTop()) {
                        TR::Node *storeNode = tt->getNode()->getStoreNode();
                        if (storeNode && (storeNode->getSymbolReference() == valueChild->getSymbolReference())) {
                            vp->_changedThis = TR_yes;
                            removeWrtBar = false;
                            break;
                        }
                    }
                } else if (vp->_changedThis == TR_yes)
                    removeWrtBar = false;
            } else if (useDefInfo) {
                uint16_t useIndex = valueChild->getUseDefIndex();
                if (useDefInfo->isUseIndex(useIndex)) {
                    TR_UseDefInfo::BitVector defs(vp->comp()->allocator());
                    if (useDefInfo->getUseDef(defs, useIndex)) {
                        TR_UseDefInfo::BitVector::Cursor cursor(defs);
                        for (cursor.SetToFirstOne(); cursor.Valid(); cursor.SetToNextOne()) {
                            int32_t defIndex = cursor;

                            if (defIndex < useDefInfo->getFirstRealDefIndex()) {
                                removeWrtBar = false;
                                break;
                            }

                            TR::TreeTop *defTree = useDefInfo->getTreeTop(defIndex);
                            TR::Node *defNode = defTree->getNode();

                            if (defNode && (defNode->getOpCodeValue() == TR::astore)
                                && (defNode->getNumChildren() > 0)) {
                                TR::Node *defChild = defNode->getFirstChild();

                                if ((defChild->getOpCodeValue() == TR::New)
                                    || (defChild->getOpCodeValue() == TR::newarray)
                                    || (defChild->getOpCodeValue() == TR::anewarray)
                                    || (defChild->getOpCodeValue() == TR::multianewarray)
                                    || (defChild && (defChild->getOpCodeValue() == TR::loadaddr)
                                        && defChild->getSymbolReference()->getSymbol()->isAuto()
                                        && defChild->getSymbolReference()->getSymbol()->isLocalObject()))
                                    removeWrtBar = true;
                                /*
                                 else if (defChild && (defChild->getOpCodeValue() == TR::aload))
                                    {
                                    TR::SymbolReference *symRef = defChild->getSymbolReference();
                                    if (symRef)
                                       {
                                       TR::Symbol *sym = symRef->getSymbol();

                                       if (!(sym->getKind() == TR::Symbol::IsStatic && sym->isConstString()))
                                           {
                                           removeWrtBar = false;
                                           break;
                                           }
                                        else
                                          removeWrtBar = true;
                                       }
                                    }
                                */
                                else {
                                    removeWrtBar = false;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }

        if (removeWrtBar
            && performTransformation(vp->comp(), "%sChanging wrtbar to store because the rhs is a new object [%p]\n",
                OPT_DETAILS, node)) {
            node->setSkipWrtBar(true);
        }
    }

    // If node is still a write barrier then let's find if it's coming from a new in the same method.
    // If that's the case then the chances are this wrtbar is going to be on a new object and we should
    // let the codegen know so it can generate better sequence.
    if ((node->getOpCodeValue() == TR::awrtbari) && !node->getSymbolReference()->getSymbol()->isArrayShadowSymbol()
        && !vp->comp()->getOption(TR_DisableWriteBarriersRangeCheck)) {
        TR::Node *objectChild = node->getFirstChild();
        bool setStackWrtBar = false;
        if ((objectChild->getOpCodeValue() == TR::aload) && objectChild->getSymbolReference()->getSymbol()->isAuto()) {
            TR_UseDefInfo *useDefInfo = vp->_useDefInfo;

            if (useDefInfo) {
                uint16_t useIndex = objectChild->getUseDefIndex();
                if (useDefInfo->isUseIndex(useIndex)) {
                    TR_UseDefInfo::BitVector defs(vp->comp()->allocator());
                    if (useDefInfo->getUseDef(defs, useIndex)) {
                        TR_UseDefInfo::BitVector::Cursor cursor(defs);
                        for (cursor.SetToFirstOne(); cursor.Valid(); cursor.SetToNextOne()) {
                            int32_t defIndex = cursor;

                            if (defIndex < useDefInfo->getFirstRealDefIndex())
                                continue;

                            TR::TreeTop *defTree = useDefInfo->getTreeTop(defIndex);
                            TR::Node *defNode = defTree->getNode();

                            if (defNode && (defNode->getOpCodeValue() == TR::astore)
                                && (defNode->getNumChildren() > 0)) {
                                TR::Node *defChild = defNode->getFirstChild();

                                if (defChild && (defChild->getOpCodeValue() == TR::loadaddr)
                                    && defChild->getSymbolReference()->getSymbol()->isAuto()
                                    && defChild->getSymbolReference()->getSymbol()->isLocalObject()) {
                                    setStackWrtBar = true;
                                    break;
                                } else if (defChild && (defChild->getOpCodeValue() == TR::aload)
                                    && defChild->getSymbolReference()->getSymbol()->isAuto()) {
                                    uint16_t useIndexA = defChild->getUseDefIndex();
                                    if (useDefInfo->isUseIndex(useIndexA)) {
                                        TR_UseDefInfo::BitVector defsA(vp->comp()->allocator());
                                        if (useDefInfo->getUseDef(defsA, useIndexA)) {
                                            TR_UseDefInfo::BitVector::Cursor cursorA(defsA);
                                            for (cursorA.SetToFirstOne(); cursorA.Valid(); cursorA.SetToNextOne()) {
                                                int32_t defIndexA = cursorA;
                                                if (defIndexA < useDefInfo->getFirstRealDefIndex())
                                                    continue;

                                                TR::TreeTop *defTreeA = useDefInfo->getTreeTop(defIndexA);
                                                TR::Node *defNodeA = defTreeA->getNode();
                                                if (defNodeA && (defNodeA->getOpCodeValue() == TR::astore)
                                                    && (defNodeA->getNumChildren() > 0)) {
                                                    TR::Node *defChildA = defNodeA->getFirstChild();
                                                    if (defChildA && (defChildA->getOpCodeValue() == TR::loadaddr)
                                                        && defChildA->getSymbolReference()->getSymbol()->isAuto()
                                                        && defChildA->getSymbolReference()
                                                               ->getSymbol()
                                                               ->isLocalObject()) {
                                                        setStackWrtBar = true;
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        if (setStackWrtBar
                            && performTransformation(vp->comp(), "%sSetting wrtbar flag to assume stack object [%p]\n",
                                OPT_DETAILS, node)) {
                            node->setLikelyStackWrtBar(true);
                        }
                    }
                }
            }
        }
    }

    // If the node is still a write barrier - we can mark it to specify the location of the dest object
    //
    if (node->getOpCode().isWrtBar()) {
        TR::Node *destinationChild = node->getOpCode().isIndirect() ? node->getChild(2) : node->getChild(1);
        bool isGlobal;
        TR::VPConstraint *constraint = vp->getConstraint(node, isGlobal);
        if (constraint) {
            if (constraint->isHeapObject() == TR_yes
                && performTransformation(vp->comp(), "%sMarking the wrtbar node [%p] - destination is a heap object\n",
                    OPT_DETAILS, node)) {
                // printf("--wbar-- heap wrtbar in %s\n", vp->comp()->signature());
                node->setIsHeapObjectWrtBar(true);
            } else if (constraint->isHeapObject() == TR_no
                && performTransformation(vp->comp(),
                    "%sMarking the wrtbar node [%p] - destination is a non-heap object\n", OPT_DETAILS, node)) {
                // printf("--wbar-- nonheap wrtbar in %s\n", vp->comp()->signature());
                node->setIsNonHeapObjectWrtBar(true);
            }
            // else
            //  printf("--wbar-- no idea in %s\n", vp->comp()->signature());
        }
    }

    return node;
}

TR::Node *constrainGoto(OMR::ValuePropagation *vp, TR::Node *node)
{
    // Put the current list of block constraints on to the edge
    //
    TR::Block *target = node->getBranchDestination()->getNode()->getBlock();
    if (vp->trace())
        traceMsg(vp->comp(), "   unconditional branch on node %s (%p), vp->_curBlock block_%d target block_%d\n",
            node->getOpCode().getName(), node, vp->_curBlock->getNumber(), target->getNumber());

    // Find the output edge from the current block that corresponds to this
    // branch
    //
    TR::CFGEdge *edge = vp->findOutEdge(vp->_curBlock->getSuccessors(), target);
    vp->printEdgeConstraints(vp->createEdgeConstraints(edge, false));
    vp->setUnreachablePath();

    return node;
}

TR::Node *constrainIgoto(OMR::ValuePropagation *vp, TR::Node *node)
{
    // Remove the current list of block constraints so they don't fall through
    //
    constrainChildren(vp, node);

    bool canFallThrough = vp->_curBlock->getNextBlock() && vp->_curBlock->getNextBlock()->isExtensionOfPreviousBlock();

    for (auto edge = vp->_curBlock->getSuccessors().begin(); edge != vp->_curBlock->getSuccessors().end(); ++edge) {
        auto current = edge;
        bool keepConstraints = (++current != vp->_curBlock->getSuccessors().end()) || canFallThrough;
        vp->printEdgeConstraints(vp->createEdgeConstraints(*edge, keepConstraints));
    }

    if (!canFallThrough)
        vp->setUnreachablePath();
    return node;
}

TR::Node *constrainReturn(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (node->getDataType() == TR::Address)
        vp->addGlobalConstraint(node, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::NotStackObject));

    // Remove the current list of block constraints so they don't fall through
    //
    constrainChildren(vp, node);
    vp->setUnreachablePath();
    return node;
}

TR::Node *constrainMonent(OMR::ValuePropagation *vp, TR::Node *node)
{
    // After the monitor enter the child must be non-null
    //
    constrainChildren(vp, node);
    vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));

    // If the child is a resolved class type, hide the class pointer in the
    // second child
    //
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (constraint && constraint->getClass()) {
        TR_OpaqueClassBlock *clazz = constraint->getClass();
        if (constraint->isClassObject() == TR_yes)
            clazz = vp->fe()->getClassClassPointer(clazz);

        if (clazz && (TR::Compiler->cls.classDepthOf(clazz) == 0) && !constraint->isFixedClass())
            clazz = NULL;

        if (node->hasMonitorClassInNode() && clazz && (node->getMonitorClassInNode() != clazz)) {
            TR_YesNoMaybe answer = vp->fe()->isInstanceOf(clazz, node->getMonitorClassInNode(), true, true);
            if (answer != TR_yes)
                clazz = node->getMonitorClassInNode();
        }

        if ((clazz || !node->hasMonitorClassInNode())
            && (performTransformation(vp->comp(), "%sSetting type on MONENTER node [%p] to [%p]\n", OPT_DETAILS, node,
                clazz)))
            node->setMonitorClassInNode(clazz);
    }
    return node;
}

TR::Node *constrainMonexit(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    // Propagate constraints so far to the exception edges
    //
    vp->createExceptionEdgeConstraints(TR::Block::CanCatchMonitorExit, NULL, node);

    // After the monitor exit the child must be non-null
    //
    vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));

    // If the child is a resolved class type, hide the class pointer in the
    // second child
    //
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (constraint && constraint->getClass()) {
        TR_OpaqueClassBlock *clazz = constraint->getClass();
        if (constraint->isClassObject() == TR_yes)
            clazz = vp->fe()->getClassClassPointer(clazz);

        if (clazz && (TR::Compiler->cls.classDepthOf(clazz) == 0) && !constraint->isFixedClass())
            clazz = NULL;

        if (node->hasMonitorClassInNode() && clazz && (node->getMonitorClassInNode() != clazz)) {
            TR_YesNoMaybe answer = vp->fe()->isInstanceOf(clazz, node->getMonitorClassInNode(), true, true);
            if (answer != TR_yes)
                clazz = node->getMonitorClassInNode();
        }

        if ((clazz || !node->hasMonitorClassInNode())
            && performTransformation(vp->comp(), "%sSetting type on MONEXIT  node [%p] to [%p]\n", OPT_DETAILS, node,
                clazz))
            node->setMonitorClassInNode(clazz);
    }

    // check for global writes and sync
    //
    // find a constraint for sync in the block
    OMR::ValuePropagation::Relationship *syncRel = vp->findConstraint(vp->_syncValueNumber);
    TR::VPSync *sync = NULL;
    if (syncRel && syncRel->constraint)
        sync = syncRel->constraint->asVPSync();
    bool syncRequired = false, syncReset = false;
    if (sync) {
        if (sync->syncEmitted() == TR_no) {
            syncRequired = true;
            if (vp->trace())
                traceMsg(vp->comp(), "Going to emit sync at monexit [%p]\n", node);
        } else if (sync->syncEmitted() == TR_yes) {
            syncReset = true;
            node->setSkipSync(true);
            if (vp->trace())
                traceMsg(vp->comp(), "syncRequired is already setup at monexit [%p]\n", node);
        }
        vp->comp()->setSyncsMarked();
    } else {
        if (vp->trace()) {
            if (sync)
                traceMsg(vp->comp(), "syncRequired is already setup at monexit [%p]\n", node);
            else
                traceMsg(vp->comp(), "No sync constraint found at monexit [%p]!\n", node);
        }
    }

    ////if (!node->isReadMonitor() || syncRequired)
    if (syncRequired) {
        node->setSkipSync(false);
        // create a constraint indicating a sync has been emitted
        if (!syncReset)
            vp->addConstraintToList(NULL, vp->_syncValueNumber, vp->AbsoluteConstraint,
                TR::VPSync::create(vp, TR_maybe), &vp->_curConstraints);
        if (vp->trace()) {
            traceMsg(vp->comp(), "Resetting syncRequired at monexit [%p]\n", node);
        }
    }

    return node;
}

TR::Node *constrainMonexitfence(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    // Propagate constraints so far to the exception edges
    //
    vp->createExceptionEdgeConstraints(TR::Block::CanCatchMonitorExit, NULL, node);

    return node;
}

TR::Node *constrainTstart(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    vp->setUnreachablePath(); // no fallthrough
    return node;
}

TR::Node *constrainTfinish(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    return node;
}

TR::Node *constrainTabort(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    return node;
}

TR::Node *constrainThrow(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    if (node->throwInsertedByOSR()) {
        // Propagate constraints so far to the exception edges
        //
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchUserThrows, NULL, node);
        vp->setUnreachablePath();
        return node;
    }

    // See if the throw can be coverted to a goto
    //
    TR::Block *predictedCatchBlock = NULL;
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node->getFirstChild(), isGlobal);

    if (debug("traceThrowToGoto")) {
        int32_t len = 1;
        const char *sig = constraint ? constraint->getClassSignature(len) : "?";
        printf("\n\n throw type %.*s\n", len, sig);
    }

    TR_OrderedExceptionHandlerIterator oehi(vp->_curBlock, vp->comp()->trMemory()->currentStackRegion());
    for (TR::Block *catchBlock = oehi.getFirst(); catchBlock; catchBlock = oehi.getNext()) {
        if (catchBlock->isOSRCatchBlock())
            continue;

        TR_YesNoMaybe willCatch = TR_maybe;
        if (catchBlock->getCatchType() == 0) {
            willCatch = TR_yes;
            if (debug("traceThrowToGoto"))
                printf("\n\n catch type yes ...\n");
        } else if (!constraint || !constraint->getClass()) {
            if (debug("traceThrowToGoto"))
                printf("\n\n thrown type unknown\n");
        } else if (catchBlock->getExceptionClass()) {
            willCatch = vp->fe()->isInstanceOf(constraint->getClass(), catchBlock->getExceptionClass(),
                constraint->isFixedClass());
            if (willCatch == TR_yes)
                vp->registerPreXClass(constraint);

            if (debug("traceThrowToGoto"))
                printf("\n\n catch type %s %.*s\n", vp->comp()->getDebug()->getName(willCatch),
                    catchBlock->getExceptionClassNameLength(), catchBlock->getExceptionClassNameChars());
        } else if (debug("traceThrowToGoto"))
            printf("\n\n catch type maybe unresolved class\n");

        if (willCatch == TR_yes) {
            predictedCatchBlock = catchBlock;
            break;
        }
        if (willCatch != TR_no && !debug("aggressiveThrowToGoto"))
            break;
    }

    if (debug("traceThrowToGoto"))
        fflush(stdout);

    if (predictedCatchBlock && !vp->comp()->getOption(TR_DisableThrowToGoto)) {
        // Temporarily set the throw's second child to be the predicted catch
        // block.
        // Later the throw will be changed to a goto.  This can't be done here
        // because it may require the creation of a new block which screws up
        // our walk through the structure
        //
        node->setSecond((TR::Node *)predictedCatchBlock);
        TR_Pair<TR::Node, TR::Block> *newPredictedThrow
            = new (vp->trStackMemory()) TR_Pair<TR::Node, TR::Block>(node, vp->_curBlock);
        vp->_predictedThrows.add(newPredictedThrow);
    }
    // Propagate constraints so far to the exception edges
    //
    vp->createExceptionEdgeConstraints(TR::Block::CanCatchUserThrows, NULL, node);
    vp->setUnreachablePath();
    return node;
}

TR::Node *constrainInstanceOf(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    // If the instance object is null, this node becomes a constant 0.
    //
    // If the instance object is non-null and provably an instance of the given
    // class type, this node becomes a constant 1.
    //
    // If the instance object is provably not an instance of the given class
    // type, this node becomes a constant 0.
    //
    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *constraint = NULL;
    TR::VPConstraint *objectConstraint = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *castConstraint = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;

    if (objectConstraint) {
        int32_t result = -1;
        if (objectConstraint->isNullObject()) {
            result = 0;
        } else if (objectConstraint->getClassType() && castConstraint && castConstraint->isFixedClass()
            && objectConstraint->getClassType() == castConstraint->getClassType() && objectConstraint->isNonNullObject()
            && objectConstraint->isClassObject() != TR_yes) {
            result = 1;
        } else if (objectConstraint->getClass() && castConstraint && castConstraint->getClass()) {
            TR_OpaqueClassBlock *objectClass = objectConstraint->getClass();
            TR_OpaqueClassBlock *castClass = castConstraint->getClass();

            bool disabled = vp->comp()->getOption(TR_DisableAOTInstanceOfInlining);

            TR_YesNoMaybe isInstance = vp->fe()->isInstanceOf(objectClass, castClass, objectConstraint->isFixedClass(),
                castConstraint->isFixedClass(), !disabled);

            if (isInstance == TR_yes) {
                if (objectConstraint->isNonNullObject()) {
                    if (castConstraint->isFixedClass()) {
                        vp->registerPreXClass(objectConstraint);
                        if (objectConstraint->isClassObject() != TR_yes)
                            result = 1;
                    }
                } else {
                    TR::Node::recreate(node, TR::acmpne);
                    vp->removeNode(node->getChild(1), true);
                    node->setAndIncChild(1, TR::Node::create(node, TR::aconst, 0, 0));
                    vp->addGlobalConstraint(node->getChild(1), TR::VPNullObject::create(vp));
                }

            } else if (isInstance == TR_no) {
                vp->registerPreXClass(objectConstraint);
                if (objectConstraint->asClass() && castConstraint->asClass()) {
                    vp->checkTypeRelationship(objectConstraint, castConstraint, result, true, false);
                    // FIXME: setting isInstance=maybe is a bit conservative
                    //  read comments for constrainCheckcast
                    //
                    if (result != 0) {
                        isInstance = TR_maybe;

                        // if the cast class is java/lang/Class
                        // then the object might be an instance and
                        // we can possibly eliminate the instanceof
#if 0
                  TR::VPClassType *castClassType = castConstraint->getClassType();
                  if (castClassType && castClassType->asResolvedClass() &&
                        (castConstraint->isClassObject() == TR_yes))
                     {
                     TR::VPResolvedClass *rc = castClassType->asResolvedClass();
                     if (rc->getClass() == vp->fe()->getClassClassPointer(rc->getClass()))
                        {
                        result = 1;
                        isInstance = TR_yes;
                        }
                     }
#endif
                    }
                } else
                    result = 0;
            }
        } else {
            if (castConstraint) {
                if (objectConstraint->asClass() && castConstraint->asClass()) {
                    vp->checkTypeRelationship(objectConstraint, castConstraint, result, true, false);
                } else {
                    TR_YesNoMaybe castIsClassObject = vp->isCastClassObject(castConstraint->getClassType());
                    if ((castIsClassObject == TR_no) && !objectConstraint->getClassType() &&
                        // objectConstraint->isNonNullObject() &&
                        (objectConstraint->isClassObject() == TR_yes)) {
                        result = 0;
                        if (vp->trace())
                            traceMsg(vp->comp(), "object is a classobject but cast is not java/lang/Class\n");
                    } else if ((castIsClassObject == TR_no) && !objectConstraint->getClassType() &&
                        // objectConstraint->isNonNullObject() &&
                        (objectConstraint->isClassObject() == TR_no)) {
                    } else if ((castIsClassObject == TR_yes) && !objectConstraint->getClassType() &&
                        // objectConstraint->isNonNullObject() &&
                        (objectConstraint->isClassObject() == TR_no)) {
                        result = 0;
                        if (vp->trace())
                            traceMsg(vp->comp(), "object is not a classobject but cast is java/lang/Class\n");
                    }
                    // probably cannot get here
                    //
                    else if ((castIsClassObject == TR_yes) && !objectConstraint->getClassType() &&

                        (objectConstraint->isClassObject() == TR_yes)) {
                        if (objectConstraint->isNonNullObject()) {
                            result = 1;
                            if (vp->trace())
                                traceMsg(vp->comp(), "object is a non-null classobject and cast is java/lang/Class\n");
                        } else {
                            TR::Node::recreate(node, TR::acmpne);
                            vp->removeNode(node->getChild(1), true);
                            node->setAndIncChild(1, TR::Node::create(node, TR::aconst, 0, 0));
                            vp->addGlobalConstraint(node->getChild(1), TR::VPNullObject::create(vp));
                        }

                    } else {
                        TR::VPConstraint *intersectConstraint = castConstraint->getClassType();
                        if (intersectConstraint) {
                            if (intersectConstraint->asFixedClass())
                                intersectConstraint = TR::VPResolvedClass::create(vp, intersectConstraint->getClass());
                            if (!objectConstraint->intersect(intersectConstraint, vp))
                                result = 0;
                        }
                    }
                }
            }
        }

        if (result >= 0) {
            constraint = TR::VPIntConst::create(vp, result);
            vp->replaceByConstant(node, constraint, lhsGlobal);
            return node;
        }
    }

    constraint = TR::VPIntRange::create(vp, 0, 1);
    vp->addGlobalConstraint(node, constraint);
    return node;
}

TR::Node *constrainCheckcast(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    // If the object is null, this node can be removed.
    //
    // If the object is provably an instance of the given class type, this node
    // can be removed.
    //
    // If the object is non-null and provably not an instance of the given
    // class type, the rest of the block can be removed, since the exception
    // will always be taken.
    //
    bool isGlobal;
    TR::VPConstraint *objectConstraint = vp->getConstraint(node->getFirstChild(), isGlobal);
    TR::VPConstraint *castConstraint = vp->getConstraint(node->getSecondChild(), isGlobal);
    TR_YesNoMaybe isInstance = TR_maybe;
    int32_t result = -1;

    if (objectConstraint) {
        if (objectConstraint->isNullObject()) {
            result = 1;
        } else if (objectConstraint == castConstraint && objectConstraint->isClassObject() != TR_yes) {
            result = 1;
        } else if (objectConstraint->getClass() && castConstraint && castConstraint->getClass()) {
            TR_OpaqueClassBlock *objectClass = objectConstraint->getClass();
            TR_OpaqueClassBlock *castClass = castConstraint->getClass();

            bool disabled = vp->comp()->getOption(TR_DisableAOTCheckCastInlining);

            isInstance = vp->fe()->isInstanceOf(objectClass, castClass, objectConstraint->isFixedClass(),
                castConstraint->isFixedClass(), !disabled);

            if (isInstance == TR_yes) {
                if (castConstraint->isFixedClass()) {
                    vp->registerPreXClass(objectConstraint);
                    if (objectConstraint->isClassObject() != TR_yes)
                        result = 1;
                }
            } else if (isInstance == TR_no
                && (objectConstraint->isNonNullObject()
                    || TR::Compiler->cls.isPrimitiveClass(vp->comp(), objectConstraint->getClass()))) {
                vp->registerPreXClass(objectConstraint);
                // this is to handle the case when either type is java/lang/Class
                //
                if (objectConstraint->asClass() && castConstraint->asClass() && objectConstraint->isNonNullObject()) {
                    vp->checkTypeRelationship(objectConstraint, castConstraint, result, false, true);
                    // reset isInstance here so that wrong constraints
                    // are not propagated below
                    //
                    // FIXME: resetting to TR_maybe is a bit conservative
                    // consider what happens for the following case
                    // (A)Class
                    // ie. checkcast
                    //          aload Class
                    //          loadaddr A
                    //
                    // in this case, we would have eliminated the rest of the
                    // block; but setting isInstance=maybe prevents that
                    // from happening
                    //
                    if (result != 0) {
                        isInstance = TR_maybe;
                    }
                } else
                    result = 0;
            }
        } else {
            if (castConstraint && objectConstraint->isNonNullObject()) {
                if (objectConstraint->asClass() && castConstraint->asClass()) {
                    vp->checkTypeRelationship(objectConstraint, castConstraint, result, false, true);
                } else {
                    TR::VPConstraint *intersectConstraint = castConstraint;
                    TR_YesNoMaybe castIsClassObject = vp->isCastClassObject(castConstraint->getClassType());
                    if (objectConstraint->asClassType() && castConstraint->asClass()
                        && castConstraint->asClass()->getClassType()) {
                        TR::VPClassType *type = castConstraint->asClass()->getClassType();
                        TR::VPClassType *dilutedType;
                        if (type && type->asFixedClass())
                            dilutedType = TR::VPResolvedClass::create(vp, type->getClass());
                        else
                            dilutedType = type;

                        if (objectConstraint->isClassObject() == TR_yes)
                            intersectConstraint = TR::VPClass::create(vp, dilutedType, NULL, NULL, NULL,
                                TR::VPObjectLocation::create(vp, TR::VPObjectLocation::ClassObject));
                        else
                            intersectConstraint = dilutedType;
                    }

                    // no need to check for nullObject here since
                    // objectConstraint is guaranteed to be a non-nullobject
                    // due to the test above

                    if ((castIsClassObject == TR_no) && !objectConstraint->getClassType()
                        && (objectConstraint->isClassObject() == TR_yes)) {
                        result = 0;
                        if (vp->trace())
                            traceMsg(vp->comp(), "object is a classobject but cast is not java/lang/Class\n");
                    } else if ((castIsClassObject == TR_no) && !objectConstraint->getClassType() &&
                        // objectConstraint->isNonNullObject() &&
                        (objectConstraint->isClassObject() == TR_no)) {
                    } else if ((castIsClassObject == TR_yes) && !objectConstraint->getClassType() &&
                        // objectConstraint->isNonNullObject() &&
                        (objectConstraint->isClassObject() == TR_no)) {
                        result = 0;
                        if (vp->trace())
                            traceMsg(vp->comp(), "object is not a classobject but cast is java/lang/Class\n");
                    }
                    // probably cannot get here
                    //
                    else if ((castIsClassObject == TR_yes) && !objectConstraint->getClassType() &&
                        // objectConstraint->isNonNullObject() &&
                        (objectConstraint->isClassObject() == TR_yes)) {
                        result = 1;
                        if (vp->trace())
                            traceMsg(vp->comp(), "object is a non-null classobject and cast is java/lang/Class\n");
                    } else if (!objectConstraint->intersect(intersectConstraint, vp))
                        result = 0;
                }
            }
        }
#if 0
      else if (castConstraint && !objectConstraint->intersect(castConstraint, vp) && objectConstraint->isNonNullObject())
         {
         result = 0;
         }
#endif
    }

    if (result == 1
        && ((node->getOpCodeValue() != TR::checkcastAndNULLCHK)
            || (objectConstraint && objectConstraint->isNonNullObject()))
        && performTransformation(vp->comp(), "%sRemoving redundant checkcast node [%p]\n", OPT_DETAILS, node)) {
        TR_ASSERT(node->getReferenceCount() == 0, "CheckCast has references");

        // Change the checkcast node into a treetop node
        //
        TR::Node *classNode = node->getSecondChild();
        vp->optimizer()->getEliminatedCheckcastNodes().add(node);
        vp->optimizer()->getClassPointerNodes().add(classNode);
        TR::Node::recreate(node, TR::treetop);
        node->setNumChildren(1);
        vp->removeNode(classNode);
        vp->setChecksRemoved();
        return node;
    }

    if (result != 1)
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchCheckCast, NULL, node);
    if ((node->getOpCodeValue() == TR::checkcastAndNULLCHK)
        && (!objectConstraint || !objectConstraint->isNonNullObject()))
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchNullCheck, NULL, node);

    bool exceptionTaken = false;
    if ((result == 0)
        || ((node->getOpCodeValue() == TR::checkcastAndNULLCHK)
            && ((objectConstraint && objectConstraint->isNullObject()) || (isInstance == TR_no)))) {
        // Everything past this point in the block is dead, since the
        // exception will always be taken.
        //
        exceptionTaken = true;
        vp->mustTakeException();
    }

    else if (castConstraint) {
        // We can constrain the object by the type of the class
        //
        TR::VPClassType *classType = castConstraint->getClassType();
        if (classType) {
            if (isInstance == TR_no)
                vp->addBlockConstraint(node->getFirstChild(), TR::VPNullObject::create(vp));
            // refine the type..commenting out the test below
            // because its conservative
            else ////if (isInstance == TR_yes)
            {
                TR::VPClassType *newClassType;
                bool castIsClassObject = false;
                // AOT returns null for getClassClass currently,
                // so don't propagate type
                //
                bool applyType = false;
                if (classType->asResolvedClass()) {
                    TR_OpaqueClassBlock *jlClass = vp->fe()->getClassClassPointer(classType->getClass());
                    if (jlClass) {
                        applyType = true;
                        if (classType->getClass() == jlClass) {
                            if (objectConstraint && objectConstraint->getClassType())
                                newClassType = TR::VPResolvedClass::create(vp, (TR_OpaqueClassBlock *)VP_SPECIALKLASS);
                            else
                                newClassType = NULL;
                            castIsClassObject = true;
                        } else {
                            // check for interfaces of Class
                            if ((classType->isClassObject() == TR_maybe)
                                && (objectConstraint && (objectConstraint->isClassObject() == TR_yes))) {
                                // not adding any new information about the type
                                // and don't want to get classobject property on one of
                                // the interfaces implemented by Class
                                //
                                applyType = false;
                            } else
                                newClassType = TR::VPResolvedClass::create(vp,
                                    classType->getClass()); // Do not want to get the 'fixed' property
                        }
                    }
                } else {
                    newClassType = classType;
                    applyType = true;
                }

                if (applyType) {
                    TR::VPConstraint *newTypeConstraint = newClassType;
                    if ((objectConstraint && (objectConstraint->isClassObject() == TR_yes)) || castIsClassObject)
                        newTypeConstraint = TR::VPClass::create(vp, newClassType, NULL, NULL, NULL,
                            TR::VPObjectLocation::create(vp, TR::VPObjectLocation::JavaLangClassObject));
                    if (newTypeConstraint)
                        vp->addBlockConstraint(node->getFirstChild(), newTypeConstraint);
                }
            }
        }
    }

    // handle checkcastAndNULLCHK here;
    // objectreference should now be injected
    // with a non-null constraint
    //
    if (!exceptionTaken && (node->getOpCodeValue() == TR::checkcastAndNULLCHK)) {
        TR::Node *child = node->getFirstChild();
        vp->addBlockConstraint(child, TR::VPNonNullObject::create(vp));
    }

    // Mark objects for tenured alignment
    if (vp->cg()->getSupportsTenuredObjectAlignment() && vp->comp()->getMethodHotness() > hot) {
        TR_ASSERT(node->getNumChildren() == 2, "checkcast should have only 2 children");
        TR::Node *valueChild = node->getFirstChild();
        TR::Node *definitionNode = NULL;
        if (valueChild->getOpCodeValue() == TR::aload) {
            TR_UseDefInfo *useDefInfo = vp->_useDefInfo;
            if (useDefInfo) {
                uint16_t useIndex = valueChild->getUseDefIndex();
                if (useDefInfo->isUseIndex(useIndex)) {
                    TR_UseDefInfo::BitVector defs(vp->comp()->allocator());
                    if (useDefInfo->getUseDef(defs, useIndex)) {
                        TR_UseDefInfo::BitVector::Cursor cursor(defs);
                        for (cursor.SetToFirstOne(); cursor.Valid(); cursor.SetToNextOne()) {
                            int32_t defIndex = cursor;

                            if (defIndex < useDefInfo->getFirstRealDefIndex())
                                continue;

                            TR::TreeTop *defTree = useDefInfo->getTreeTop(defIndex);
                            TR::Node *defNode = defTree->getNode();

                            if (defNode && (defNode->getOpCodeValue() == TR::astore)) {
                                TR_OpaqueMethodBlock *ownMethod = defNode->getOwningMethod();
                                char buf[512];
                                const char *methodSig = vp->fe()->sampleSignature(ownMethod, buf, 512, vp->trMemory());
                                if (methodSig
                                    && !strncmp(methodSig,
                                        "java/util/HashMap.get(Ljava/lang/Object;)Ljava/lang/Object;", 59)) {
                                    definitionNode = defNode;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }

#ifdef J9_PROJECT_SPECIFIC
        if (definitionNode && node->getSecondChild()->getOpCodeValue() == TR::loadaddr) {
            TR::SymbolReference *classSymRef = node->getSecondChild()->getSymbolReference();
            if (!classSymRef->isUnresolved()) {
                TR::StaticSymbol *classSym = classSymRef->getSymbol()->getStaticSymbol();
                if (classSym) {
                    uint32_t size
                        = TR::Compiler->cls.classInstanceSize((TR_OpaqueClassBlock *)classSym->getStaticAddress());
                    if (vp->cg()->isObjectOfSizeWorthAligning(size)) {
                        vp->comp()->fej9()->markClassForTenuredAlignment(vp->comp(),
                            (TR_OpaqueClassBlock *)classSym->getStaticAddress(), 0);
                    }
                }
            }
        }
#endif
    }

    return node;
}

TR::Node *constrainBCDCHK(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    vp->createExceptionEdgeConstraints(TR::Block::CanCatchUserThrows, NULL, node);
    return node;
}

TR::Node *constrainCheckcastNullChk(OMR::ValuePropagation *vp, TR::Node *node) { return constrainCheckcast(vp, node); }

TR::Node *constrainNew(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    vp->createExceptionEdgeConstraints(TR::Block::CanCatchNew, NULL, node);

    // The constraints on the child can be applied to this node. We also know
    // that the type is fixed and non-null.
    //
    bool isGlobal;
    TR::VPConstraint *classConstraint = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (classConstraint) {
        // since loadaddrs are now primed with a ClassObject property,
        // this property should not be propagated to the 'new' unless the
        // underlying type of the loadaddr is a java/lang/Class
        if (classConstraint->getClass() && !classConstraint->isFixedClass())
            vp->addGlobalConstraint(node, TR::VPFixedClass::create(vp, classConstraint->getClass()));
        else if (classConstraint->asClass() && classConstraint->asClass()->getClassType()
            && !(classConstraint->asClass()->getClassType()->isClassObject() == TR_yes))
            vp->addGlobalConstraint(node, classConstraint->asClass()->getClassType());
        else
            vp->addGlobalConstraint(node, classConstraint);

        // If it's an abstract class, or an interface, or a value type, the allocation
        // cannot be removed because InstantiationError could be thrown
        TR_OpaqueClassBlock *clazz
            = classConstraint->getClassType() ? classConstraint->getClassType()->getClass() : NULL;
        if (clazz && TR::Compiler->cls.isConcreteClass(vp->comp(), clazz)
            && !TR::Compiler->cls.isValueTypeClass(clazz)) {
            node->setAllocationCanBeRemoved(true);
        }
    }

    vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));

    node->setIsNonNull(true);

    return node;
}

TR::Node *constrainNewvalue(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    vp->createExceptionEdgeConstraints(TR::Block::CanCatchNewvalue, NULL, node);

    bool trace = vp->trace();

    // The constraints on the child can be applied to this node. We also know
    // that the type is fixed and non-null.
    //
    bool isGlobal;
    TR::VPConstraint *classConstraint = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (classConstraint) {
        // since loadaddrs are now primed with a ClassObject property,
        // this property should not be propagated to the 'newvalue' unless the
        // underlying type of the loadaddr is a java/lang/Class
        //
        // If the underlying type of the loadaddr is a java/lang/Class, the property can be propagated to 'new'
        //
        if (classConstraint->getClass() && !classConstraint->isFixedClass()) {
            vp->addGlobalConstraint(node, TR::VPFixedClass::create(vp, classConstraint->getClass()));
        } else if (classConstraint->asClass() && classConstraint->asClass()->getClassType()
            && !(classConstraint->asClass()->getClassType()->isClassObject() == TR_yes)) {
            vp->addGlobalConstraint(node, classConstraint->asClass()->getClassType());
        } else {
            vp->addGlobalConstraint(node, classConstraint);
        }

        // - If newvalue is used to create a non value type instance, the allocation cannot be removed
        //   because IncompatibleClassChangeError needs to be thrown.
        // - No need to check if it's an abstract class because value type cannot be abstract class.
        // - If the referenced value class doesn't belongs to the same nest as the current class, IllegalAccessError
        //   needs to be thrown and allocation cannot be removed.
        TR_OpaqueClassBlock *clazz
            = classConstraint->getClassType() ? classConstraint->getClassType()->getClass() : NULL;
        TR_OpaqueClassBlock *currentClazz = vp->comp()->getCurrentMethod()->classOfMethod();

        if (clazz && TR::Compiler->cls.isValueTypeClass(clazz)
            && TR::Compiler->cls.isClassVisible(vp->comp(), currentClazz, clazz)) {
            node->setAllocationCanBeRemoved(true);
        }
    }

    vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));

    node->setIsNonNull(true);

    return node;
}

TR::Node *constrainNewArray(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    TR::Node *sizeNode = node->getFirstChild();
    TR::Node *typeNode = node->getSecondChild();

    vp->createExceptionEdgeConstraints(TR::Block::CanCatchArrayNew, NULL, node);

    TR_ASSERT(typeNode->getOpCodeValue() == TR::iconst, "assertion failure");
    int32_t arrayType = typeNode->getInt();

    // See if there are already constraints on the array size. If the size is
    // known to be negative an exception will always be thrown and we can
    // remove the rest of the block.
    //
    bool isGlobal;
    TR::VPConstraint *sizeConstraint = vp->getConstraint(sizeNode, isGlobal);
    int64_t maxSize = TR::Compiler->om.maxArraySizeInElementsForAllocation(node, vp->comp());
    if (sizeConstraint && (sizeConstraint->getHighInt() < 0 || sizeConstraint->getLowInt() > maxSize)) {
        vp->mustTakeException();
        // node->setAllocationCanBeRemoved(false)
        return node;
    } else if (!sizeConstraint) {
        dumpOptDetails(vp->comp(), "size node has no known constraint for newarray %p\n", sizeNode);
        // node->setAllocationCanBeRemoved(false)
    } else if (sizeConstraint->getLowInt() >= 0 && sizeConstraint->getHighInt() <= maxSize) {
        // If array size is known to be non-negative and the number of elements will
        // not exceed the maximum array size, allocation will not throw an exception
        node->setAllocationCanBeRemoved(true);
    }

    // The array size (the first child) can be constrained because of memory
    // limitations on the allocation.
    //
    // TODO - should be sizeConstraint = vp->addBlockConstraint(sizeNode, TR::VPIntRange::create(vp, 0, maxSize));
    if (maxSize < TR::getMaxSigned<TR::Int32>()) {
        vp->addBlockConstraint(sizeNode, TR::VPIntRange::create(vp, 0, static_cast<int32_t>(maxSize)));
        sizeConstraint = vp->getConstraint(sizeNode, isGlobal);
    }

    // Type constraints can be applied to this node.
    //
    int32_t elementSize = TR::Compiler->om.getSizeOfArrayElement(node);
    TR_OpaqueClassBlock *clazz = vp->fe()->getClassFromNewArrayType(arrayType);
    if (clazz)
        vp->addGlobalConstraint(node, TR::VPFixedClass::create(vp, clazz));
    vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
    if (sizeConstraint)
        vp->addGlobalConstraint(node,
            TR::VPArrayInfo::create(vp, sizeConstraint->getLowInt(), sizeConstraint->getHighInt(), elementSize));
    else
        vp->addGlobalConstraint(node,
            TR::VPArrayInfo::create(vp, 0, static_cast<int32_t>(TR::getMaxSigned<TR::Int32>()), elementSize));

    node->setIsNonNull(true);
    return node;
}

TR::Node *constrainVariableNew(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    node->setIsNonNull(true);
    return node;
}

TR::Node *constrainVariableNewArray(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    TR::Node *typeNode = node->getSecondChild();
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(typeNode, isGlobal);
    TR_OpaqueClassBlock *elementType;
    if (constraint && constraint->getClassType() && constraint->getClassType()->asFixedClass()
        && constraint->isNonNullObject() && (elementType = constraint->getClass())) {
        if (TR::Compiler->cls.isPrimitiveClass(vp->comp(), elementType)) {
            TR::Node::recreateWithoutProperties(node, TR::newarray, node->getNumChildren(),
                vp->comp()->getSymRefTab()->findOrCreateNewArraySymbolRef(
                    typeNode->getSymbolReference()->getOwningMethodSymbol(vp->comp())));
            TR::Node *typeConst
                = TR::Node::create(TR::iconst, 0, vp->comp()->fe()->getNewArrayTypeFromClass(constraint->getClass()));
            vp->_curTree->insertBefore(OMR::TreeTop::create(vp->comp(), TR::Node::create(TR::treetop, 1, typeNode)));
            node->setAndIncChild(1, typeConst);
            typeNode->recursivelyDecReferenceCount();
        }
#ifdef J9_PROJECT_SPECIFIC
        else {
            TR::Node::recreateWithoutProperties(node, TR::anewarray, node->getNumChildren(),
                vp->comp()->getSymRefTab()->findOrCreateANewArraySymbolRef(
                    typeNode->getSymbolReference()->getOwningMethodSymbol(vp->comp())));
            if (typeNode->getOpCodeValue() != TR::loadaddr) {
                TR::Node *loadaddr = TR::Node::createWithSymRef(TR::loadaddr, 0,
                    vp->comp()->getSymRefTab()->findOrCreateClassSymbol(
                        typeNode->getSymbolReference()->getOwningMethodSymbol(vp->comp()), 0, elementType));
                vp->_curTree->insertBefore(
                    OMR::TreeTop::create(vp->comp(), TR::Node::create(TR::treetop, 1, typeNode)));
                node->setAndIncChild(1, loadaddr);
                typeNode->recursivelyDecReferenceCount();
            }
        }
#endif
    }
    node->setIsNonNull(true);
    return node;
}

TR::Node *constrainANewArray(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    TR::Node *sizeNode = NULL;
    TR::Node *typeNode = NULL;
    sizeNode = node->getFirstChild();
    typeNode = node->getSecondChild();

    vp->createExceptionEdgeConstraints(TR::Block::CanCatchArrayNew, NULL, node);

    bool isGlobal;
    TR::VPConstraint *typeConstraint = vp->getConstraint(typeNode, isGlobal);
    TR_ASSERT(typeConstraint, "Class child of anewarray has no constraint");

    // See if there are already constraints on the array size. If the size is
    // known to be negative an exception will always be thrown and we can
    // remove the rest of the block.
    //
    int32_t elementSize = TR::Compiler->om.getSizeOfArrayElement(node);
    int64_t maxSize = TR::Compiler->om.maxArraySizeInElementsForAllocation(node, vp->comp());

    TR::VPConstraint *sizeConstraint = vp->getConstraint(sizeNode, isGlobal);
    if (sizeConstraint && (sizeConstraint->getHighInt() < 0 || sizeConstraint->getLowInt() > maxSize)) {
        vp->mustTakeException();
        // node->setAllocationCanBeRemoved(false)
        return node;
    } else if (!sizeConstraint) {
        dumpOptDetails(vp->comp(), "size node has no known constraint for anewarray %p\n", sizeNode);
        // node->setAllocationCanBeRemoved(false)
    } else if (sizeConstraint->getLowInt() >= 0 && sizeConstraint->getHighInt() <= maxSize) {
        // If array size is known to be non-negative, the number of elements
        // will not exceed the maximum array size, and the type of the
        // array is known and resolved, allocation will not throw an exception
        if (typeConstraint && typeConstraint->getClassType() && typeConstraint->getClassType()->getClass()) {
            TR_OpaqueClassBlock *arrayClass
                = vp->fe()->getArrayClassFromComponentClass(typeConstraint->getClassType()->getClass());
            if (arrayClass)
                node->setAllocationCanBeRemoved(true);
        }
    }

    // The array size (the first child) can be constrained because of memory
    // limitations on the allocation.
    //
    // TODO - should be sizeConstraint = vp->addBlockConstraint(sizeNode, TR::VPIntRange::create(vp, 0, maxSize));
    if (maxSize < TR::getMaxSigned<TR::Int32>()) {
        vp->addBlockConstraint(sizeNode, TR::VPIntRange::create(vp, 0, static_cast<int32_t>(maxSize)));
        sizeConstraint = vp->getConstraint(sizeNode, isGlobal);
    }

    // Type constraints can be applied to this node.
    //
    if (typeConstraint && typeConstraint->getClassType()) {
        typeConstraint = typeConstraint->getClassType()->getArrayClass(vp);

        if (typeConstraint) {
            // since loadaddrs are now primed with a ClassObject property,
            // this property should not be propagated to the 'newarray' unless the
            // underlying type of the loadaddr is a java/lang/Class
            if (typeConstraint->getClass() && !typeConstraint->isFixedClass())
                typeConstraint = TR::VPFixedClass::create(vp, typeConstraint->getClass());
            ////else if (typeConstraint->asClass() && typeConstraint->asClass()->getClassType() &&
            ////          !(typeConstraint->asClass()->getClassType()->isClassObject() == TR_yes))
            ////   typeConstraint = typeConstraint->asClass()->getClassType();
            vp->addGlobalConstraint(node, typeConstraint);
        }
    }
    vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
    if (sizeConstraint) {
        vp->addGlobalConstraint(node,
            TR::VPArrayInfo::create(vp, sizeConstraint->getLowInt(), sizeConstraint->getHighInt(), elementSize));
    } else
        vp->addGlobalConstraint(node,
            TR::VPArrayInfo::create(vp, 0, static_cast<int32_t>(TR::getMaxSigned<TR::Int32>()), elementSize));

    node->setIsNonNull(true);
    return node;
}

TR::Node *constrainMultiANewArray(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    // The first child is the number of dimensions. It should be a constant equal
    // to the number of children - 2.
    //
    TR_ASSERT(node->getFirstChild()->getOpCode().isLoadConst()
            && node->getFirstChild()->getInt() == node->getNumChildren() - 2,
        "Bad number of dims for multianewarray");

    int32_t numChildren = node->getNumChildren();

    TR::Node *typeNode = node->getChild(numChildren - 1);

    vp->createExceptionEdgeConstraints(TR::Block::CanCatchArrayNew, NULL, node);

    bool isGlobal;
    TR::VPConstraint *typeConstraint = vp->getConstraint(typeNode, isGlobal);
    TR_ASSERT(typeConstraint, "Class child of multianewarray has no constraint");

    // See if there are already constraints on the array size. If any size is
    // known to be negative an exception will always be thrown and we can
    // remove the rest of the block.
    //
    int32_t maxSize = (int32_t)TR::Compiler->om.maxArraySizeInElementsForAllocation(node,
        vp->comp()); // int32_t is ok because multiANewArray is only useful in Java, where array sizes are bound by
                     // TR::getMaxSigned<TR::Int32>()
    int64_t maxHeapSize;

    if (vp->comp()->compileRelocatableCode()) {
        maxHeapSize = -1;
    } else {
        maxHeapSize = TR::Compiler->vm.maxHeapSizeInBytes();
    }

    int32_t maxDimSize = static_cast<int32_t>(TR::getMaxSigned<TR::Int32>());
    if (maxHeapSize > 0) {
        int64_t maxHeapSizeInPointers = maxHeapSize / TR::Compiler->om.sizeofReferenceField();
        if (maxHeapSizeInPointers < maxDimSize)
            maxDimSize = (int32_t)maxHeapSizeInPointers;
    }

    TR::Node *child = NULL;
    TR::VPClassType *newType = typeConstraint->getClassType();
    TR::VPConstraint *sizeConstraint = NULL;
    for (int32_t i = numChildren - 2; i > 0; i--) {
        child = node->getChild(i);
        sizeConstraint = vp->getConstraint(child, isGlobal);
        int32_t maxHigh = maxDimSize;
        if (i == numChildren - 2) // last dimension
            maxHigh = maxSize;

        if (sizeConstraint && (sizeConstraint->getHighInt() < 0 || sizeConstraint->getLowInt() > maxHigh)) {
            vp->mustTakeException();
            return node;
        }

        // The array size can be constrained because of memory limitations on the
        // allocation.
        //
        vp->addBlockConstraint(child, TR::VPIntRange::create(vp, 0, maxHigh));

        // Build up the eventual type of the array
        // TODO - enable this when getArrayClass is correct
        //
        // newType = newType->getArrayClass(vp);
    }

    sizeConstraint = vp->getConstraint(node->getSecondChild(), isGlobal);

    int32_t elementSize;
    if (numChildren - 2 == 1) // dims == 1
    {
        // A 1 dim array is NOT an array of pointers to arrays!
        // We need to determine the size of the element type defined by the type node
        TR::SymbolReference *symRef = typeNode->getSymbolReference();
        int32_t len;
        const char *sig = symRef->getTypeSignature(len);
        if (sig == NULL)
            return (node);
        elementSize = arrayElementSize(sig, len, typeNode, vp);
        if (elementSize == 0)
            return (node);
    } else {
        elementSize = TR::Compiler->om.sizeofReferenceField();
    }

    // Type constraints can be applied to this node.
    //
    TR::VPArrayInfo *arrayInfo
        = TR::VPArrayInfo::create(vp, sizeConstraint->getLowInt(), sizeConstraint->getHighInt(), elementSize);
    vp->addGlobalConstraint(node,
        TR::VPClass::create(vp, newType, TR::VPNonNullObject::create(vp), NULL, arrayInfo,
            TR::VPObjectLocation::create(vp, TR::VPObjectLocation::NotClassObject)));

    node->setIsNonNull(true);
    return node;
}

// bit opcode constraints start

static TR::VPLongRange *getLongRange(TR::VPConstraint *c) { return c->asLongRange(); }

static TR::VPLongConst *getLongConst(TR::VPConstraint *c) { return c->asLongConst(); }

static TR::VPIntRange *getIntRange(TR::VPConstraint *c) { return c->asIntRange(); }

static TR::VPIntConst *getIntConst(TR::VPConstraint *c) { return c->asIntConst(); }

static void getLowHighInts(TR::VPIntRange *range, int32_t &low, int32_t &high)
{
    low = range->getLowInt();
    high = range->getHighInt();
}

static void getInt(TR::VPIntConst *c, int32_t &n) { n = c->getInt(); }

static void getLowHighLongs(TR::VPLongRange *range, int64_t &low, int64_t &high)
{
    low = range->getLowLong();
    high = range->getHighLong();
}

static void getLong(TR::VPLongConst *c, int64_t &n) { n = c->getLong(); }

static TR::VPConstraint *createIntConstConstraint(OMR::ValuePropagation *vp, int32_t n)
{
    return TR::VPIntConst::create(vp, n);
}

template<typename T> static TR::VPConstraint *createIntRangeConstraint(OMR::ValuePropagation *vp, T l, T h)
{
    return TR::VPIntRange::create(vp, static_cast<int32_t>(l), static_cast<int32_t>(h));
}

static TR::VPConstraint *createLongConstConstraint(OMR::ValuePropagation *vp, int64_t n)
{
    return TR::VPLongConst::create(vp, n);
}

static TR::VPConstraint *createLongRangeConstraint(OMR::ValuePropagation *vp, int64_t l, int64_t h)
{
    return TR::VPLongRange::create(vp, l, h);
}

static int32_t integerToPowerOf2(int32_t n) { return (n == 0) ? 0 : floorPowerOfTwo(n); }

static int32_t integerNumberOfLeadingZeros(int32_t n) { return leadingZeroes(n); }

static int32_t integerNumberOfTrailingZeros(int32_t n) { return trailingZeroes(n); }

static int32_t integerBitCount(int32_t n) { return populationCount(n); }

static int32_t integerLowestOneBit(int32_t n) { return (n == 0) ? 0 : 1 << integerNumberOfTrailingZeros(n); }

static int64_t longToPowerOf2(int64_t n) { return (n == 0) ? 0 : floorPowerOfTwo64(n); }

static int32_t longNumberOfLeadingZeros(int64_t n) { return leadingZeroes(n); }

static int32_t longNumberOfTrailingZeros(int64_t n) { return trailingZeroes(n); }

static int32_t longBitCount(int64_t n) { return populationCount(n); }

static int64_t longLowestOneBit(int64_t n) { return (n == 0) ? 0 : 1 << longNumberOfTrailingZeros(n); }

template<typename FUNC, typename FUNC2, typename T>
void addValidRangeBlockOrGlobalConstraint(OMR::ValuePropagation *vp, TR::Node *node, FUNC createRange,
    FUNC2 processValue, T low, T high, bool childGlobal)
{
    T pLow = processValue(low), pHigh = processValue(high);

    if (pLow > pHigh) {
        std::swap(pLow, pHigh);
    }

    if (vp->trace()) {
        traceMsg(vp->comp(), "Adding a %s range constraint %lld .. %lld on the node %p\n",
            (childGlobal) ? "global" : "block", pLow, pHigh, node);
    }

    vp->addBlockOrGlobalConstraint(node, createRange(vp, pLow, pHigh), childGlobal);
}

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename T>
static TR::Node *constrainHighestOneBitAndLeadingZerosHelper(OMR::ValuePropagation *vp, TR::Node *node, A getConst,
    B getRange, C getValue, D getValues, E createConst, F createRange, G processValue, T MIN_VALUE, T MAX_VALUE)
{
    TR_ASSERT(node->getNumChildren() == 1, "Node has a wrong number of children (i.e. !=1 )! ");
    constrainChildren(vp, node);

    if (vp->trace()) {
        traceMsg(vp->comp(), "calling constrainHighestOneBitAndLeadingZerosHelper for node %p\n", node);
    }

    bool childGlobal;
    TR::VPConstraint *childConstraint = vp->getConstraint(node->getFirstChild(), childGlobal);

    if (childConstraint) {
        if (getConst(childConstraint)) {
            T value = 0;
            getValue(getConst(childConstraint), value);

            // RangeConstraint with the same low and high should be folded into a const constraint
            MIN_VALUE = value;
            MAX_VALUE = value;

            if (vp->trace()) {
                traceMsg(vp->comp(), "The first child's value of %p %lld is replaced with %lld \n", node, value,
                    processValue(value));
            }

        } else if (getRange(childConstraint)) {
            T low = 0, high = 0;
            getValues(getRange(childConstraint), low, high);
            if (low < 0 && high < 0) {
                // RangeConstraint with the same low and high should be folded into a const constraint.
                // All negative numbers have the highest bit set
                MIN_VALUE = MAX_VALUE;
                if (vp->trace()) {
                    traceMsg(vp->comp(),
                        "Constraint %lld .. %lld of %p 's first child is negative and folded into %lld \n", low, high,
                        node, processValue(MAX_VALUE));
                }
            } else if (low >= 0 && high >= 0) {
                MIN_VALUE = low;
                MAX_VALUE = high;
            }
            // The else case is the same as [min_processValue .. max_processValue]
        }
    }

    addValidRangeBlockOrGlobalConstraint(vp, node, createRange, processValue, MIN_VALUE, MAX_VALUE, childGlobal);
    return node;
}

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename T>
static TR::Node *constrainLowestOneBitAndTrailingZerosHelper(OMR::ValuePropagation *vp, TR::Node *node, A getConst,
    B getRange, C getValue, D getValues, E createConst, F createRange, G processValue, T MIN_VALUE, T MAX_VALUE)
{
    TR_ASSERT(node->getNumChildren() == 1, "Node has a wrong number of children (i.e. !=1 )! ");
    constrainChildren(vp, node);

    if (vp->trace()) {
        traceMsg(vp->comp(), "calling constrainLowestOneBitAndTrailingZerosHelper for node %p\n", node);
    }

    bool childGlobal;
    TR::VPConstraint *childConstraint = vp->getConstraint(node->getFirstChild(), childGlobal);
    if (childConstraint && getConst(childConstraint)) {
        T value = 0;
        getValue(getConst(childConstraint), value);
        MIN_VALUE = value;
        MAX_VALUE = value;
    }

    addValidRangeBlockOrGlobalConstraint(vp, node, createRange, processValue, MIN_VALUE, MAX_VALUE, childGlobal);
    return node;
}

TR::Node *constrainIntegerHighestOneBit(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainHighestOneBitAndLeadingZerosHelper(vp, node, getIntConst, getIntRange, getInt, getLowHighInts,
        createIntConstConstraint,
        // createIntRangeConstraint, integerToPowerOf2, (int32_t) 0, (int32_t) -1);
        createIntRangeConstraint<int32_t>, integerToPowerOf2, (int32_t)TR::getMaxSigned<TR::Int32>(),
        (int32_t)TR::getMinSigned<TR::Int32>());
}

TR::Node *constrainIntegerNumberOfLeadingZeros(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainHighestOneBitAndLeadingZerosHelper(vp, node, getIntConst, getIntRange, getInt, getLowHighInts,
        createIntConstConstraint, createIntRangeConstraint<int32_t>, integerNumberOfLeadingZeros, (int32_t)0,
        (int32_t)-1);
}

TR::Node *constrainLongHighestOneBit(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainHighestOneBitAndLeadingZerosHelper(vp, node, getLongConst, getLongRange, getLong, getLowHighLongs,
        createLongConstConstraint,
        // createLongRangeConstraint, longToPowerOf2, (int64_t) 0, (int64_t) -1);
        createLongRangeConstraint, longToPowerOf2, (int64_t)TR::getMaxSigned<TR::Int64>(),
        TR::getMinSigned<TR::Int64>());
}

TR::Node *constrainLongNumberOfLeadingZeros(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainHighestOneBitAndLeadingZerosHelper(vp, node, getLongConst, getLongRange, getLong, getLowHighLongs,
        createIntConstConstraint, createIntRangeConstraint<int64_t>, longNumberOfLeadingZeros, (int64_t)0, (int64_t)-1);
}

TR::Node *constrainIntegerLowestOneBit(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainLowestOneBitAndTrailingZerosHelper(vp, node, getIntConst, getIntRange, getInt, getLowHighInts,
        createIntConstConstraint, createIntRangeConstraint<int32_t>, integerLowestOneBit,
        (int32_t)TR::getMaxSigned<TR::Int32>(), (int32_t)TR::getMinSigned<TR::Int32>());
}

TR::Node *constrainIntegerBitCount(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainLowestOneBitAndTrailingZerosHelper(vp, node, getIntConst, getIntRange, getInt, getLowHighInts,
        createIntConstConstraint, createIntRangeConstraint<int32_t>, integerBitCount, (int32_t)0, (int32_t)-1);
}

TR::Node *constrainIntegerNumberOfTrailingZeros(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainLowestOneBitAndTrailingZerosHelper(vp, node, getIntConst, getIntRange, getInt, getLowHighInts,
        createIntConstConstraint, createIntRangeConstraint<int32_t>, integerNumberOfTrailingZeros, (int32_t)0,
        (int32_t)-1);
}

TR::Node *constrainLongLowestOneBit(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainLowestOneBitAndTrailingZerosHelper(vp, node, getLongConst, getLongRange, getLong, getLowHighLongs,
        createLongConstConstraint, createLongRangeConstraint, longLowestOneBit, (int64_t)TR::getMaxSigned<TR::Int64>(),
        (int64_t)TR::getMinSigned<TR::Int64>());
}

TR::Node *constrainLongNumberOfTrailingZeros(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainLowestOneBitAndTrailingZerosHelper(vp, node, getLongConst, getLongRange, getLong, getLowHighLongs,
        createIntConstConstraint, createIntRangeConstraint<int64_t>, longNumberOfTrailingZeros, (int64_t)0,
        (int64_t)-1);
}

TR::Node *constrainLongBitCount(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainLowestOneBitAndTrailingZerosHelper(vp, node, getLongConst, getLongRange, getLong, getLowHighLongs,
        createIntConstConstraint, createIntRangeConstraint<int64_t>, longBitCount, (int64_t)0, (int64_t)-1);
}

// bit opcodes constraints end

// Handles TR::arraylength and TR::contigarraylength
//
TR::Node *constrainArraylength(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;

    constrainChildren(vp, node);

    int32_t lowerBoundLimit = 0;
    int32_t upperBoundLimit = static_cast<int32_t>(TR::getMaxSigned<TR::Int32>());
    int32_t elementSize = 0;

    // See if the underlying array has bound limits
    //
    TR::Node *objectRef = node->getFirstChild();

    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(objectRef, isGlobal);

    bool isKnownObj;

    vp->getArrayLengthLimits(constraint, lowerBoundLimit, upperBoundLimit, elementSize, isKnownObj);

    // If the element size is still not known, try to get it from the node or
    // from the signature, and then propagate it back down to the object ref.
    //
    if (!elementSize) {
        elementSize = node->getArrayStride();

        if (!elementSize && constraint) {
            int32_t len = 0;
            const char *sig = constraint->getClassSignature(len);
            if (sig)
                elementSize = arrayElementSize(sig, len, objectRef, vp);
        }

        if (elementSize) {
            constraint = TR::VPArrayInfo::create(vp, lowerBoundLimit, upperBoundLimit, elementSize);
            vp->addBlockOrGlobalConstraint(objectRef, constraint, isGlobal);
        }
    }

    // See if the array length is constant
    //
    if (lowerBoundLimit == upperBoundLimit) {
        // A constant can't be used if a contiguity check is still required.
        //
        if (!(node->getOpCodeValue() == TR::contigarraylength || node->getOpCodeValue() == TR::discontigarraylength)
            || !TR::Compiler->om.isDiscontiguousArray(lowerBoundLimit, elementSize)) {
            constraint = TR::VPIntConst::create(vp, lowerBoundLimit);
            vp->replaceByConstant(node, constraint, isGlobal);
            return node;
        } else {
            int32_t intValue = lowerBoundLimit;
            if (node->getOpCodeValue() == TR::contigarraylength)
                intValue = 0;

            constraint = TR::VPIntConst::create(vp, intValue);
            vp->replaceByConstant(node, constraint, isGlobal);
            return node;
        }
    }

    uint32_t shiftAmount = 0;
    if (elementSize > 1) {
        if (elementSize == 2)
            shiftAmount = 1;
        else if (elementSize == 4)
            shiftAmount = 2;
        else
            shiftAmount = 3;

        int64_t maxSize = TR::Compiler->om.maxArraySizeInElements(elementSize, vp->comp());
        if (maxSize < upperBoundLimit) {
            TR_ASSERT(0 <= maxSize && maxSize <= TR::getMaxSigned<TR::Int32>(),
                "arraylength must be within Int32 range");
            upperBoundLimit = (int32_t)maxSize;
        }
    }

    // For hybrid arraylets, a constant can't be used if a contiguity check is still required.
    //
    if (!(node->getOpCodeValue() == TR::contigarraylength || node->getOpCodeValue() == TR::discontigarraylength)
        || (lowerBoundLimit != upperBoundLimit)
        || !TR::Compiler->om.isDiscontiguousArray(upperBoundLimit, elementSize)) {
        constraint = TR::VPIntRange::create(vp, lowerBoundLimit, upperBoundLimit);
    } else {
        if (node->getOpCodeValue() == TR::contigarraylength) {
            int32_t maxContiguousArraySizeInElements = TR::Compiler->om.maxContiguousArraySizeInBytes() >> shiftAmount;
            if (lowerBoundLimit > maxContiguousArraySizeInElements) {
                lowerBoundLimit = 0;
                upperBoundLimit = 0;
            } else {
                lowerBoundLimit = 0;
                upperBoundLimit = maxContiguousArraySizeInElements;
            }
        }

        constraint = TR::VPIntRange::create(vp, lowerBoundLimit, upperBoundLimit);
    }

    if (constraint) {
        vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
    }

    if (!node->getArrayStride()
        && performTransformation(vp->comp(), "%sSetting element width for array [%p] to %d\n", OPT_DETAILS, node,
            elementSize)) {
        node->setArrayStride(elementSize);
    }

    if (!vp->_curTree->getNode()->getOpCode().isNullCheck())
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));

    node->setIsNonNegative(true);
    node->setCannotOverflow(true);
    return node;
}

static TR::MethodSymbol *refineMethodSymbolInCall(OMR::ValuePropagation *vp, TR::Node *node,
    TR::SymbolReference *symRef, TR_ResolvedMethod *resolvedMethod, int32_t offset)
{
    TR::SymbolReference *newSymRef = vp->comp()->getSymRefTab()->findOrCreateMethodSymbol(
        symRef->getOwningMethodIndex(), -1, resolvedMethod, TR::MethodSymbol::Virtual);
    newSymRef->copyAliasSets(symRef, vp->comp()->getSymRefTab());
    newSymRef->setOffset(offset);
    TR::MethodSymbol *methodSymbol = newSymRef->getSymbol()->castToMethodSymbol();
    node->setSymbolReference(newSymRef);
    if (vp->trace())
        traceMsg(vp->comp(), "Refined method symbol to %s\n", resolvedMethod->signature(vp->trMemory()));
    return methodSymbol;
}

static void devirtualizeCall(OMR::ValuePropagation *vp, TR::Node *node)
{
    TR::SymbolReference *symRef = node->getSymbolReference();
    TR::MethodSymbol *methodSymbol = symRef->getSymbol()->castToMethodSymbol();
    bool interfaceCall = methodSymbol->isInterface();

    if (!methodSymbol->firstArgumentIsReceiver()) {
        if (vp->trace())
            traceMsg(vp->comp(), "Not attempting to de-virtualize call [%p] without first argument receiver\n", node);
        return;
    }

    if (!vp->comp()->fe()->canDevirtualizeDispatch())
        return;

    int32_t firstArgIndex = node->getFirstArgumentIndex();
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node->getChild(firstArgIndex), isGlobal);
    TR_OpaqueClassBlock *thisType = NULL;
    if (!constraint || !(thisType = constraint->getClass())) {
        if (vp->trace())
            traceMsg(vp->comp(), "Interface call [%p] to %s with unknown object type in %s\n", node,
                methodSymbol->getMethod()->signature(vp->trMemory(), stackAlloc), vp->comp()->signature());

        static bool dontProfileMore = feGetEnv("TR_DontProfileMoreAtHot") ? true : false;

        if (!dontProfileMore && (vp->comp()->getMethodHotness() == hot) && vp->comp()->getRecompilationInfo()
            && vp->_isGlobalPropagation && vp->lastTimeThrough() && vp->optimizer()->switchToProfiling()) {
            dumpOptDetails(vp->comp(), "%s enabling profiling: would be useful to profile arraycopy calls\n",
                OPT_DETAILS);
        }

        return;
    }

    // If thisType represents an array, change it to a fixed reference to
    // java/lang/Object for the purposes of devirtualization.
    if (constraint->getArrayInfo()) {
#ifdef J9_PROJECT_SPECIFIC
        thisType = vp->comp()->getObjectClassPointer();
        if (!thisType) {
            if (vp->trace())
                traceMsg(vp->comp(), "Not attempting to de-virtualize call [%p] with array receiver\n", node);
            return;
        }
        constraint = TR::VPFixedClass::create(vp, thisType);
#endif
    }

    if ((constraint->asClass() && (constraint->asClass()->isClassObject() == TR_yes))
        || (constraint->asObjectLocation() && (constraint->asObjectLocation()->isClassObject() == TR_yes))) {
#ifdef J9_PROJECT_SPECIFIC
        thisType = vp->comp()->getClassClassPointer();
        if (!thisType) {
            if (vp->trace())
                traceMsg(vp->comp(), "Not attempting to de-virtualize call [%p] with class object receiver\n", node);
            return;
        }
        constraint = TR::VPFixedClass::create(vp, thisType);
#endif
    }

    // The fixed type may be different than the type of the this pointer.
    // For example,
    //    A a = new B();
    //    a.foo(); // the fixed type is B
    //
    // Find the resolved method for the fixed type.
    //
    TR_ResolvedMethod *owningMethod = symRef->getOwningMethod(vp->comp());
    TR_ResolvedMethod *resolvedMethod;
    TR_ResolvedMethod *originalResolvedMethod = 0;
    TR_OpaqueClassBlock *originalMethodClass;

    int32_t offset;
    int32_t len;
    const char *s;
    int32_t cpIndex = symRef->getCPIndex();
    bool needsGuard = false;

    TR::Node *argNode = node->getChild(firstArgIndex);
    if (argNode->getSymbol()) {
        TR::Symbol *argSym = argNode->getSymbol();
        if (argSym->isParm()) {
            if (!vp->isParmInvariant(argSym)) {
                if (vp->trace())
                    traceMsg(vp->comp(),
                        "Not attempting to de-virtualize call [%p] because receiver [%p] is not invariant\n", node,
                        argNode);
                return;
            }
        }
    }

    if (methodSymbol->isComputed()) {
#ifdef J9_PROJECT_SPECIFIC
        if (methodSymbol->getRecognizedMethod() == TR::java_lang_invoke_MethodHandle_invokeExact
            && !symRef->isUnresolved()) {
            TR::Node *receiver = node->getArgument(0);
            TR::KnownObjectTable *knot = vp->comp()->getOrCreateKnownObjectTable();
            TR::VPConstraint *constraint = vp->getConstraint(receiver, isGlobal);
            if (constraint && constraint->getKnownObject() && knot) {
                TR::ResolvedMethodSymbol *owningMethod = symRef->getOwningMethodSymbol(vp->comp());
                resolvedMethod = vp->comp()->fej9()->createMethodHandleArchetypeSpecimen(vp->trMemory(),
                    knot->getPointerLocation(constraint->getKnownObject()->getIndex()),
                    owningMethod->getResolvedMethod());
                if (resolvedMethod) {
                    TR::SymbolReference *specimenSymRef = vp->getSymRefTab()->findOrCreateMethodSymbol(
                        owningMethod->getResolvedMethodIndex(), -1, resolvedMethod, TR::MethodSymbol::ComputedVirtual);
                    if (performTransformation(vp->comp(), "%sSubstituting more specific method symbol on %p: %s\n",
                            OPT_DETAILS, node, specimenSymRef->getName(vp->getDebug()))) {
                        node->setSymbolReference(specimenSymRef);
                    } else {
                        return;
                    }
                    if (vp->optimizer()->getOptimization(OMR::methodHandleInvokeInliningGroup)->requested()) {
                        if (vp->trace())
                            traceMsg(vp->comp(),
                                "Not inlining call [%p] because the MethodHandle.invoke inlining group will do a "
                                "better job\n",
                                node);
                        return;
                    }
                } else {
                    if (vp->trace())
                        traceMsg(vp->comp(),
                            "Not inlining call [%p] because there is no more specific method symbol for obj%d\n", node,
                            constraint->getKnownObject()->getIndex());
                    return;
                }
            } else {
                if (vp->trace())
                    traceMsg(vp->comp(),
                        "Not inlining call [%p] because unable to substitute object-specific method symbol\n", node);
                return;
            }
        }
#endif
    } else if (methodSymbol->isInterface()) {
        if (TR::Compiler->cls.isInterfaceClass(vp->comp(), thisType)) {
            if (vp->trace())
                traceMsg(vp->comp(),
                    "Not attempting to de-virtualize interface call [%p] with interface-class receiver\n", node);
            return;
        }

        int32_t cpIndex = symRef->getCPIndex();

        TR::Method *originalMethod = methodSymbol->getMethod();
        len = originalMethod->classNameLength();
        s = TR::Compiler->cls.classNameToSignature(originalMethod->classNameChars(), len, vp->comp());
        originalMethodClass = vp->fe()->getClassFromSignature(s, len, owningMethod);

        if (!originalMethodClass) {
            if (vp->trace())
                traceMsg(vp->comp(),
                    "Not attempting to de-virtualize interface call [%p] with receiver class %.*s not yet loaded\n",
                    node, len, s);
            return;
        }

        resolvedMethod = owningMethod->getResolvedInterfaceMethod(vp->comp(), thisType, cpIndex);
        if (!resolvedMethod) {
            if (vp->trace())
                traceMsg(vp->comp(), "Not attempting to de-virtualize interface call [%p] with no resolved method\n",
                    node, len, s);
            return;
        }

        offset = owningMethod->getResolvedInterfaceMethodOffset(thisType, cpIndex);
    } else {
        TR_ASSERT(methodSymbol->isVirtual(), "unexpected method symbol kind for %s node %p",
            node->getOpCode().getName(), node);

        TR::ResolvedMethodSymbol *resolvedMethodSymbol = methodSymbol->getResolvedMethodSymbol();
        if (!resolvedMethodSymbol || symRef == vp->getSymRefTab()->findObjectNewInstanceImplSymbol())
            return;

        originalResolvedMethod = resolvedMethodSymbol->getResolvedMethod();
        originalMethodClass = originalResolvedMethod->classOfMethod();

        if ((vp->fe()->isInstanceOf(thisType, originalMethodClass, true, true) != TR_yes)
            || (originalMethodClass && TR::Compiler->cls.isInterfaceClass(vp->comp(), originalMethodClass)))
            return;

        if (!constraint->isFixedClass()) {
            if (!constraint->isPreexistentObject()) {
                if (originalMethodClass == thisType)
                    return;

                s = node->getChild(firstArgIndex)->getTypeSignature(len);
                if (s && vp->fe()->getClassFromSignature(s, len, owningMethod) == thisType)
                    return;
            }
        }

        offset = static_cast<int32_t>(symRef->getOffset());
        resolvedMethod = owningMethod->getResolvedVirtualMethod(vp->comp(), thisType, offset);
        if (!resolvedMethod)
            return;
    }

    if (node->isTheVirtualCallNodeForAGuardedInlinedCall()) {
#ifdef J9_PROJECT_SPECIFIC
        TR_PersistentClassInfo *classInfo
            = vp->comp()->getPersistentInfo()->getPersistentCHTable()->findClassInfoAfterLocking(thisType, vp->comp());
        if (!classInfo || !classInfo->isInitialized())
            return;
#else
        return;
#endif
    }

    if (methodSymbol->isVirtual() || methodSymbol->isInterface()) {
        // This initial attempt to refine the call symbol applies only to virtual
        // calls. We want to be able to do this for interface calls too but we
        // currently can't in general, since we need to preserve the behaviour of
        // invokeinterface, which throws IllegalAccessError when dispatch lands
        // on a non-public method.
        //
        // Maybe TODO? Get the type information we have to the inliner, to
        // prevent interface->virtual transformation without inhibiting inlining.
        if (methodSymbol->isVirtual()
            && (!originalResolvedMethod || !resolvedMethod->isSameMethod(originalResolvedMethod))) {
            // The virtual guard noping code relies on the the virtual call node's symbol to
            // refer to the original devirtualized call.  Not devirtualizing the call shouldn't be
            // a performance concern since the call is in a cold block.
            //
            // Why do we need to stop the devirtualization if its a
            // guarded virtual ? Commenting these two lines out so that a guarded virtual gets
            // added to the devirtualizedCalls list and the guard is eventually removed. This
            // has the benefit that the call no longer interferes with aliasing (and other opts
            // like escape analysis).
            //
            if (performTransformation(vp->comp(), "%sDevirtualizing call [%p] to %s\n", OPT_DETAILS, node,
                    resolvedMethod->signature(vp->trMemory())))
                methodSymbol = refineMethodSymbolInCall(vp, node, symRef, resolvedMethod, offset);
        } else if (!resolvedMethod->virtualMethodIsOverridden() && !constraint->isFixedClass())
            return;

        if (constraint->isFixedClass() || constraint->isPreexistentObject()) {
            if (!performTransformation(vp->comp(), "%sChanging an indirect call %s (%s) to a direct call [%p]\n",
                    OPT_DETAILS, resolvedMethod->signature(vp->trMemory()), node->getOpCode().getName(), node))
                return;

            // change the opcode to be a direct call
            //

            if (!vp->registerPreXClass(constraint) && constraint->isPreexistentObject()) {
                if (!resolvedMethod->virtualMethodIsOverridden()
                    && (constraint->getClass() == constraint->getPreexistence()->getAssumptionClass()))
                    vp->_prexMethods.add(resolvedMethod);
                else
                    return;
            }

            // If we know the exact callee, then we can still transform even
            // interface calls. Fix the symbol now since it wasn't done earlier.
            // The callee should be public since it was found by
            // getResolvedInterfaceMethod.
            if (methodSymbol->isInterface())
                methodSymbol = refineMethodSymbolInCall(vp, node, symRef, resolvedMethod, offset);
            TR::Node::recreate(node, methodSymbol->getMethod()->directCallOpCode());

            // remove the generated first argument
            //
            TR_ASSERT(firstArgIndex == 1, "more than one generated argument on an indirect call");
            node->getChild(0)->recursivelyDecReferenceCount();
            int32_t numChildren = node->getNumChildren();
            for (int32_t i = 1; i < numChildren;
                 i = (++i & 0x0000ffff)) // this & with 0x0000ffff is to work around an opt bug in the ibm build on ia32
                node->setChild(i - 1, node->getChild(i));
            node->setNumChildren(numChildren - 1);
            firstArgIndex--;
        }
    }

    TR_PrexArgInfo *argInfo
        = new (vp->trStackMemory()) TR_PrexArgInfo(node->getNumChildren() - firstArgIndex, vp->trMemory());
    bool tracePrex
        = vp->trace() || vp->comp()->trace(OMR::inlining) || vp->comp()->trace(OMR::invariantArgumentPreexistence);
    if (tracePrex)
        traceMsg(vp->comp(), "PREX.vp: Value propagation populating prex argInfo for %s %p\n",
            node->getOpCode().getName(), node);
    for (int32_t c = node->getNumChildren() - 1; c >= firstArgIndex; --c) {
        TR::Node *argument = node->getChild(c);
        if (argument->getDataType() == TR::Address) {
            TR::VPConstraint *constr = vp->getConstraint(argument, isGlobal);
            if (constr) {
                // TODO: constr is most likely a TR::VPClass constraint, so we should
                // be calling constr->getKnownObject().  Should fix this in dev.
                //
                // Note also that if prex were to start using VP constraints, there
                // would be no need to check isNonNullObject here any more.  That
                // check is only needed because prex arg info's known object info
                // is NOT predicated on the reference being non-null.
                //
                if (constr->asKnownObject() && constr->isNonNullObject()) {
                    argInfo->set(c - firstArgIndex,
                        new (vp->trStackMemory()) TR_PrexArgument(constr->asKnownObject()->getIndex(), vp->comp()));
                    if (tracePrex)
                        traceMsg(vp->comp(), "PREX.vp:    Child %d [%p] arg %p is known object obj%d\n", c, argument,
                            argInfo->get(c - firstArgIndex), constr->asKnownObject()->getIndex());
                } else if (constr->isFixedClass()) {
                    argInfo->set(c - firstArgIndex,
                        new (vp->trStackMemory()) TR_PrexArgument(TR_PrexArgument::ClassIsFixed, constr->getClass()));
                    if (tracePrex) {
                        TR_OpaqueClassBlock *clazz = constr->getClass();
                        const char *sig = TR::Compiler->cls.classSignature(vp->comp(), clazz, vp->trMemory());
                        traceMsg(vp->comp(), "PREX.vp:    Child %d [%p] arg %p has fixed class %p %s\n", c, argument,
                            argInfo->get(c - firstArgIndex), clazz, sig);
                    }
                } else if (constr->isPreexistentObject()) {
                    argInfo->set(c - firstArgIndex,
                        new (vp->trStackMemory()) TR_PrexArgument(TR_PrexArgument::ClassIsPreexistent));
                    if (tracePrex)
                        traceMsg(vp->comp(), "PREX.vp:    Child %d [%p] arg %p is preexistent\n", c, argument,
                            argInfo->get(c - firstArgIndex));
                }
            }
        }
    }
    if (tracePrex)
        traceMsg(vp->comp(), "PREX.vp: Done populating prex argInfo for %s %p\n", node->getOpCode().getName(), node);

    if ((vp->lastTimeThrough() || !node->getOpCode().isIndirect()) && (vp->_isGlobalPropagation || !vp->getLastRun())
        && !methodSymbol->isInterface())
        vp->_devirtualizedCalls.add(new (vp->trStackMemory()) OMR::ValuePropagation::CallInfo(vp, thisType, argInfo));
    vp->invalidateUseDefInfo();
    vp->invalidateValueNumberInfo();
}

static bool canFoldNonOverriddenGuard(OMR::ValuePropagation *vp, TR::Node *callNode, TR::Node *guardNode)
{
    TR::SymbolReference *symRef = callNode->getSymbolReference();
    TR::MethodSymbol *methodSymbol = symRef->getSymbol()->castToMethodSymbol();

    int32_t firstArgIndex = callNode->getFirstArgumentIndex();
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(callNode->getChild(firstArgIndex), isGlobal);
    TR_OpaqueClassBlock *thisType;
    dumpOptDetails(vp->comp(), "Guard %p Call %p constraint %p\n", guardNode, callNode, constraint);
    if (constraint && constraint->isFixedClass() && (thisType = constraint->getClass()) && methodSymbol->isVirtual()) {
        TR::ResolvedMethodSymbol *resolvedMethodSymbol = methodSymbol->getResolvedMethodSymbol();
        // The receiver class has to be initialized at this point. Otherwise updateCHTable might
        // not have been called to update overridden bits
        if (resolvedMethodSymbol && TR::Compiler->cls.isClassInitialized(vp->comp(), thisType)) {
            TR_ResolvedMethod *originalResolvedMethod = resolvedMethodSymbol->getResolvedMethod();
            TR_OpaqueClassBlock *originalMethodClass = originalResolvedMethod->classOfMethod();

            if ((vp->fe()->isInstanceOf(thisType, originalMethodClass, true, true) == TR_yes)
                && !originalResolvedMethod->virtualMethodIsOverridden()) {
                TR_VirtualGuard *virtualGuard = vp->comp()->findVirtualGuardInfo(guardNode);
                if (virtualGuard && virtualGuard->canBeRemoved())
                    return true;
            }
        }
    }
    return false;
}

TR::Node *constrainCall(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    if (vp->lastTimeThrough() && vp->_isGlobalPropagation) {
        CallNodeToGuardNodesMap *map = vp->_callNodeToGuardNodes;
        CallNodeToGuardNodesMap::const_iterator it = map->find(node);

        if (it != map->end()) {
            List<TR_Pair<TR::TreeTop, TR::CFGEdge> > *list = it->second;
            ListIterator<TR_Pair<TR::TreeTop, TR::CFGEdge> > iter(list);
            TR_Pair<TR::TreeTop, TR::CFGEdge> *pair = iter.getFirst();

            for (; pair; pair = iter.getNext()) {
                TR::TreeTop *treeTop = pair->getKey();
                TR::Node *guardNode = treeTop->getNode();
                TR::CFGEdge *edge = pair->getValue();

                dumpOptDetails(vp->comp(), "Found saved guard %p for call %p\n", guardNode, node);

                TR_ASSERT_FATAL(guardNode->isTheVirtualGuardForAGuardedInlinedCall()
                        && guardNode->isNonoverriddenGuard(),
                    "Should be NonoverriddenGuard");

                if (canFoldNonOverriddenGuard(vp, node, guardNode)) {
                    dumpOptDetails(vp->comp(), "can fold this time\n");
                    vp->removeNode(guardNode, false);
                    TR::TransformUtil::removeTree(vp->comp(), treeTop);
                    vp->setEnableSimplifier();
                    vp->_edgesToBeRemoved->add(edge);
                }
            }
        }
    }

    if (node->getSymbolReference()->isOSRInductionHelper()) {
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchOSR, NULL, node);
    } else {
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchUserThrows, NULL, node);
    }

    TR::MethodSymbol *symbol = node->getSymbol()->castToMethodSymbol();

    bool callIsDirect = !node->getOpCode().isIndirect();
#ifdef J9_PROJECT_SPECIFIC
    TR::RecognizedMethod rm = symbol->getRecognizedMethod();
    // Look for recognized converters call.
    if (vp->comp()->isConverterMethod(rm) && vp->comp()->canTransformConverterMethod(rm)) {
        if (!vp->_converterCalls.find(vp->_curTree))
            vp->_converterCalls.add(vp->_curTree);
    }

    if (vp->lastTimeThrough() && callIsDirect && TR::Compiler->om.canGenerateArraylets()
        && node->getSymbol()->getMethodSymbol()->getMethod()
        && (node->getSymbol()->getMethodSymbol()->getMethod()->isUnsafeWithObjectArg()
            || (node->getSymbol()->getMethodSymbol()->getMethod()->isUnsafeCAS()
                && vp->comp()->getOption(TR_EnableInliningOfUnsafeForArraylets))))

    {
        // printf("adding method %s to inline to list - found in %s\n",
        // resolvedMethod->signature(vp->comp()->trMemory()),vp->comp()->signature());fflush(stdout);
        bool operandGlobal;
        TR::VPConstraint *operand = vp->getConstraint(node->getChild(1), operandGlobal);
        if (operand
            && ((operand->isClassObject() == TR_yes)
                || (operand->getClassType()
                    && ((operand->getClassType()->isArray() == TR_no)
                        || (operand->getClassType()->isClassObject() == TR_yes))))) {
            vp->_unsafeCallsToInline.add(new (vp->trStackMemory()) OMR::ValuePropagation::CallInfo(vp, NULL, NULL));
            node->setUnsafeGetPutCASCallOnNonArray();
            // printf("change flag for node  %p\n",node);fflush(stdout);
            if (vp->trace())
                traceMsg(vp->comp(), "change unsafe flag for node  [%p]\n", node);
        }
    }
#endif

    // For an indirect call, see if it can be devirtualized
    //
    if (node->getOpCode().isIndirect() && !debug("dontDevirtualize")) {
        devirtualizeCall(vp, node);
        symbol = node->getSymbol()->castToMethodSymbol();
        if (node->getSymbolReference()->isUnresolved() && symbol->isInterface()) {
            char *sig = symbol->getMethod()->classNameChars();
            int32_t len = symbol->getMethod()->classNameLength();
            sig = TR::Compiler->cls.classNameToSignature(sig, len, vp->comp());
            TR_ResolvedMethod *method = node->getSymbolReference()->getOwningMethod(vp->comp());
            TR::VPConstraint *constraint = TR::VPUnresolvedClass::create(vp, sig, len, method);
            int32_t firstArgIndex = node->getFirstArgumentIndex();
            bool isGlobal;
            TR::VPConstraint *existingConstraint = vp->getConstraint(node->getChild(firstArgIndex), isGlobal);
            if (existingConstraint && existingConstraint->intersect(constraint, vp)) {
                // dumpOptDetails(vp->comp(), "Reached here for call node %p in %s\n", node, vp->comp()->signature());
            } else {
                // dumpOptDetails(vp->comp(), "First time an interface call made using receiver at node %p in %s\n",
                // node, vp->comp()->signature());
                vp->addBlockConstraint(node->getChild(firstArgIndex), constraint);
            }
        }
    }

    if (node->getType().isInt32() && vp->comp()->getOption(TR_AllowVPRangeNarrowingBasedOnDeclaredType)) {
        // If this node is a TR::icalli then there may be
        // some value in parsing the return type of the method
        // being invoked to obtain better info about byte/bool/char/short
        //
        TR::Method *method = node->getSymbol()->castToMethodSymbol()->getMethod();
        if (method) {
            TR::DataType dataType = method->returnType();

            if (dataType == TR::Int8 || dataType == TR::Int16 || dataType == TR::Int32 || dataType == TR::Int64) {
                bool isUnsigned = method->returnTypeIsUnsigned();
                TR::VPConstraint *constraint = TR::VPIntRange::create(vp, dataType, isUnsigned ? TR_yes : TR_no);
                if (constraint)
                    vp->addGlobalConstraint(node, constraint);
            }
        }
    }

    vp->constrainRecognizedMethod(node);

    // Return if the node is not a call anymore
    if (!node->getOpCode().isCall())
        return node;

    // Symbol might have been changed, get it from node again
    symbol = node->getSymbol()->castToMethodSymbol();
    if (symbol) {
#ifdef J9_PROJECT_SPECIFIC
        bool isIntegerAbs = (symbol->getRecognizedMethod() == TR::java_lang_Math_abs_I);
        bool isLongAbs = (symbol->getRecognizedMethod() == TR::java_lang_Math_abs_L);
        bool isFloatAbs = (symbol->getRecognizedMethod() == TR::java_lang_Math_abs_F);
        bool isDoubleAbs = (symbol->getRecognizedMethod() == TR::java_lang_Math_abs_D);

        if (isIntegerAbs || isLongAbs) {
            // one special case to worry about here: if the operand of the ABS could be the minimum
            // value of the range (TR::getMinSigned<TR::Int32>() or TR::getMinSigned<TR::Int64>()) then the return value
            // of the ABS could be that same minimum value (yes, that's right, it could be negative).
            bool operandGlobal;
            TR::VPConstraint *operand = vp->getConstraint(node->getFirstChild(), operandGlobal);
            TR::VPConstraint *absConstraint = NULL;
            TR::VPConstraint *positiveConstraint = NULL;
            bool couldBeMinValue = false;

            if (isIntegerAbs) {
                positiveConstraint = TR::VPIntRange::create(vp, 0, TR::getMaxSigned<TR::Int32>());
                couldBeMinValue = (operand == NULL || operand->getLowInt() == TR::getMinSigned<TR::Int32>());
            } else {
                TR_ASSERT(isLongAbs, ""); // sanity check that no other cases get in here by mistake
                positiveConstraint = TR::VPLongRange::create(vp, 0, TR::getMaxSigned<TR::Int64>());
                couldBeMinValue = (operand == NULL || operand->getLowLong() == TR::getMinSigned<TR::Int64>());
            }

            if (couldBeMinValue) {
                TR::VPConstraint *minValueConstraint = NULL;
                if (isIntegerAbs)
                    minValueConstraint = TR::VPIntConst::create(vp, TR::getMinSigned<TR::Int32>());
                else {
                    TR_ASSERT(isLongAbs, ""); // sanity check that no other cases get in here by mistake
                    minValueConstraint = TR::VPLongConst::create(vp, TR::getMinSigned<TR::Int64>());
                }
                absConstraint = TR::VPMergedConstraints::create(vp, minValueConstraint, positiveConstraint);
            } else
                absConstraint = positiveConstraint;
            vp->addGlobalConstraint(node, absConstraint);
            if (!couldBeMinValue)
                node->setIsNonNegative(true);
            node->setCannotOverflow(true);
        } else if (isFloatAbs || isDoubleAbs) {
            // TODO: when we implement FP constraints, need to add the 0,MAX_F or 0,MAX_D constraint here
        } else if (symbol->getRecognizedMethod() == TR::java_lang_Class_getComponentType) {
            TR::Node *classPointer = node->getFirstChild();
            if (classPointer->getOpCode().hasSymbolReference()
                && (classPointer->getSymbolReference() == vp->comp()->getSymRefTab()->findVftSymbolRef())) {
                bool operandGlobal;
                TR::VPConstraint *operand = vp->getConstraint(classPointer->getFirstChild(), operandGlobal);
                if (operand) {
                    TR::VPArrayInfo *arrayInfo = operand->getArrayInfo();
                    if (arrayInfo || (operand->getClassType() && (operand->getClassType()->isArray() == TR_yes))) {
                        vp->_javaLangClassGetComponentTypeCalls.add(node);
                    }
                }
            }
        } else if (symbol->getRecognizedMethod() == TR::java_lang_Integer_numberOfLeadingZeros
            || symbol->getRecognizedMethod() == TR::java_lang_Integer_numberOfTrailingZeros) {
            vp->addGlobalConstraint(node, TR::VPIntRange::create(vp, 0, 32));
        } else if (symbol->getRecognizedMethod() == TR::java_lang_Long_numberOfLeadingZeros
            || symbol->getRecognizedMethod() == TR::java_lang_Long_numberOfTrailingZeros) {
            vp->addGlobalConstraint(node, TR::VPIntRange::create(vp, 0, 64));
        } else if (callIsDirect &&
            // symbol->isNative() &&
            (symbol->getRecognizedMethod() == TR::java_lang_J9VMInternals_identityHashCode)) {
            // new hashcode algorithm
            //
            TR_OpaqueClassBlock *jitHelpersClass = vp->comp()->getJITHelpersClassPointer();
            if (jitHelpersClass && TR::Compiler->cls.isClassInitialized(vp->comp(), jitHelpersClass)) {
                TR::SymbolReference *hashCodeMethodSymRef = NULL;
                TR::SymbolReference *getHelpersSymRef = NULL;
                TR_ScratchList<TR_ResolvedMethod> helperMethods(vp->trMemory());
                vp->comp()->fej9()->getResolvedMethods(vp->trMemory(), jitHelpersClass, &helperMethods);
                ListIterator<TR_ResolvedMethod> it(&helperMethods);
                for (TR_ResolvedMethod *m = it.getCurrent(); m; m = it.getNext()) {
                    char *sig = m->nameChars();
                    if (!strncmp(sig, "hashCodeImpl", 11)) {
                        hashCodeMethodSymRef = vp->comp()->getSymRefTab()->findOrCreateMethodSymbol(JITTED_METHOD_INDEX,
                            -1, m, TR::MethodSymbol::Virtual);
                        hashCodeMethodSymRef->setOffset(
                            TR::Compiler->cls.vTableSlot(vp->comp(), m->getPersistentIdentifier(), jitHelpersClass));
                    } else if (!strncmp(sig, "getHelpers", 10)) {
                        getHelpersSymRef = vp->comp()->getSymRefTab()->findOrCreateMethodSymbol(JITTED_METHOD_INDEX, -1,
                            m, TR::MethodSymbol::Static);
                    }
                }

                if (vp->comp()->getOption(TR_EnableJITHelpershashCodeImpl) && hashCodeMethodSymRef && getHelpersSymRef
                    && performTransformation(vp->comp(), "%sChanging call to new hashCodeImpl at node [%p]\n",
                        OPT_DETAILS, node)) {
                    // FIXME: add me to the list of calls to be inlined
                    //
                    TR::Method *method = getHelpersSymRef->getSymbol()->castToMethodSymbol()->getMethod();
                    TR::Node *helpersCallNode
                        = TR::Node::createWithSymRef(node, method->directCallOpCode(), 0, getHelpersSymRef);
                    TR::TreeTop *helpersCallTT
                        = TR::TreeTop::create(vp->comp(), TR::Node::create(TR::treetop, 1, helpersCallNode));
                    vp->_curTree->insertBefore(helpersCallTT);

                    method = hashCodeMethodSymRef->getSymbol()->castToMethodSymbol()->getMethod();
                    TR::Node::recreate(node, method->directCallOpCode());
                    TR::Node *removedChild = node->getFirstChild();
                    // vp->removeNode(removedChild);
                    removedChild->recursivelyDecReferenceCount();
                    // TR::Node *objChild = node->getSecondChild();
                    // node->removeAllChildren();
                    // firstChild->decReferenceCount();
                    node->setNumChildren(2);
                    node->setAndIncChild(0, helpersCallNode);
                    // node->setAndIncChild(1, objChild);
                    node->setSymbolReference(hashCodeMethodSymRef);

                    vp->invalidateUseDefInfo();
                    vp->invalidateValueNumberInfo();
                    return node;
                }
            }
        } else if (symbol->getRecognizedMethod() == TR::sun_misc_Unsafe_ensureClassInitialized) {
            /*
            n572n       vcall  sun/misc/Unsafe.ensureClassInitialized(Ljava/lang/Class;)V[#631  final native virtual
            Method -728] n577n         aload  java/lang/invoke/MethodHandle$UnsafeGetter.myUnsafe n579n         aloadi
            java/lang/invoke/MethodHandle.defc

            Because Unsafe.ensureClassInitialized is JNI, can't use direct JNI, otherwise can't optimized it at compile
            time.
            */
            TR::Node *classValueNode = node->getSecondChild();
            bool isGlobal;
            TR::VPConstraint *constraint = vp->getConstraint(classValueNode, isGlobal);
            if (constraint && constraint->getKnownObject() && constraint->isClassObject() == TR_yes
                && constraint->isNonNullObject()) {
                TR::VPKnownObject *knowConstraint = constraint->getKnownObject();
                /* get known object class initialization status
                 * 1. get J9class of known object
                 * 2. get J9class->initializeStatus
                 * 3. if J9class->initializeStatus value is 1, remove current call.
                 */

                TR_OpaqueClassBlock *j9class = knowConstraint->getClass();
                if (j9class && TR::Compiler->cls.isClassInitialized(vp->comp(), j9class)
                    && performTransformation(vp->comp(),
                        "%s Remove TR::sun_misc_Unsafe_ensureClassInitialized call, class already initialized [%p]\n",
                        OPT_DETAILS, node)) {
                    TR::Node *receiver = node->getChild(0);
                    TR::TransformUtil::transformCallNodeToPassThrough(vp, node, vp->_curTree, receiver);
                    return node;
                }
            }
        }
#endif
    }

    if (node->getOpCode().isIndirect() && !vp->_curTree->getNode()->getOpCode().isNullCheck()
        && owningMethodDoesNotContainNullChecks(vp, node))
        vp->addBlockConstraint(node->getFirstChild(), TR::VPNonNullObject::create(vp));

    // sync required
    OMR::ValuePropagation::Relationship *syncRel = vp->findConstraint(vp->_syncValueNumber);
    TR::VPSync *sync = NULL;
    if (syncRel && syncRel->constraint)
        sync = syncRel->constraint->asVPSync();
    if (sync && sync->syncEmitted() == TR_yes) {
        vp->addConstraintToList(NULL, vp->_syncValueNumber, vp->AbsoluteConstraint, TR::VPSync::create(vp, TR_maybe),
            &vp->_curConstraints);
        if (vp->trace()) {
            traceMsg(vp->comp(), "Setting syncRequired due to node [%p]\n", node);
        }
    } else {
        if (vp->trace()) {
            if (sync)
                traceMsg(vp->comp(), "syncRequired is already setup at node [%p]\n", node);
            else
                traceMsg(vp->comp(), "No sync constraint found at node [%p]!\n", node);
        }
    }

    return node;
}

TR::Node *constrainAcall(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainCall(vp, node);

    // Return if the node is not a call anymore
    if (!node->getOpCode().isCall())
        return node;

    return vp->innerConstrainAcall(node);
}

// handles unsigned too
TR::Node *constrainAdd(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;

    bool longAdd = node->getOpCode().isLong();
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    TR::VPConstraint *constraint = NULL;
    if (lhs && rhs) {
        constraint = lhs->add(rhs, node->getDataType(), vp);
        if (constraint) {
            if (longAdd) {
                if (constraint->asLongConst()) {
                    vp->replaceByConstant(node, constraint, lhsGlobal);
                    return node;
                }
            } else {
                if (constraint->asIntConst()) {
                    vp->replaceByConstant(node, constraint, lhsGlobal);
                    return node;
                }
            }

            bool didReduction = false;
            if (longAdd)
                didReduction = reduceLongOpToIntegerOp(vp, node, constraint);

            vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
            // If we reduced long to integer, node is now an i2l; not safe to do any further addition-related things
            // with node
            if (didReduction)
                return node;
        }
    }

    // If the rhs is a constant create a relative constraint between this node
    // and the lhs child
    //
    if (rhs) {
        constraint = NULL;
        if (rhs->asLongConst()) {
            if (rhs->asLongConst()->getLong() > TR::getMinSigned<TR::Int32>()
                && rhs->asLongConst()->getLong() < TR::getMaxSigned<TR::Int32>())
                constraint = TR::VPEqual::create(vp, (int32_t)rhs->asLongConst()->getLong());
        } else if (rhs->asIntConst()) {
            // TODO: propagate the unsigned property here
            //  to the TR::VPRelation constraint
            //  the code below is conservative for unsigned &
            //  we can do better (for unsigned) by checking the
            //  appropriate ranges; but the downside is that the 'increment'
            //  of the Equal constraint needs to be used in the right
            //  type context
            if (rhs->asIntConst()->getInt() > TR::getMinSigned<TR::Int32>()
                && rhs->asIntConst()->getInt() < TR::getMaxSigned<TR::Int32>())
                constraint = TR::VPEqual::create(vp, rhs->asIntConst()->getInt());
        }
        if (constraint) {
            if (rhsGlobal)
                vp->addGlobalConstraint(node, constraint, node->getFirstChild());
            else
                vp->addBlockConstraint(node, constraint, node->getFirstChild());
        }
    }

    if (longAdd) {
        if (vp->isHighWordZero(node))
            node->setIsHighWordZero(true);
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

// handles unsigned subtract
TR::Node *constrainSubtract(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;

    bool longSub = node->getOpCode().isLong();
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    TR::VPConstraint *constraint = NULL;
    if (lhs && rhs) {
        constraint = lhs->subtract(rhs, node->getDataType(), vp);
        if (constraint) {
            if (longSub) {
                if (constraint->asLongConst()) {
                    vp->replaceByConstant(node, constraint, lhsGlobal);
                    return node;
                }
            } else {
                if (constraint->asIntConst()) {
                    vp->replaceByConstant(node, constraint, lhsGlobal);
                    return node;
                }

                if (constraint->asShortConst()) {
                    vp->replaceByConstant(node, constraint, lhsGlobal);
                    return node;
                }
            }

            bool didReduction = false;
            if (longSub)
                didReduction = reduceLongOpToIntegerOp(vp, node, constraint);
            vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
            // If we reduced long to integer, node is now an i2l; not safe to do any further subtract-related things
            // with node
            if (didReduction)
                return node;
        }
    }

    // If the rhs is a constant create a relative constraint between this node
    // and the lhs child
    //
    if (rhs) {
        constraint = NULL;
        if (rhs->asLongConst()) {
            if (rhs->asLongConst()->getLong() > TR::getMinSigned<TR::Int32>()
                && rhs->asLongConst()->getLong() < TR::getMaxSigned<TR::Int32>())
                constraint = TR::VPEqual::create(vp, -(int32_t)rhs->asLongConst()->getLong());
        } else if (rhs->asIntConst()) {
            // TODO: propagate unsigned property
            // to the TR::VPRelation constraint;
            // we can do better for unsigned here
            // see comment above for add
            if (rhs->asIntConst()->getInt() > TR::getMinSigned<TR::Int32>()
                && rhs->asIntConst()->getInt() < TR::getMaxSigned<TR::Int32>())
                constraint = TR::VPEqual::create(vp, -rhs->asIntConst()->getInt());
        } else if (rhs->asShortConst()) {
            if (rhs->asShortConst()->getShort() > TR::getMinSigned<TR::Int16>()
                && rhs->asShortConst()->getShort() < TR::getMaxSigned<TR::Int16>())
                constraint = TR::VPEqual::create(vp, -rhs->asShortConst()->getShort());
        }
        if (constraint) {
            if (rhsGlobal)
                vp->addGlobalConstraint(node, constraint, node->getFirstChild());
            else
                vp->addBlockConstraint(node, constraint, node->getFirstChild());
        }
    }

    if (longSub) {
        if (vp->isHighWordZero(node))
            node->setIsHighWordZero(true);
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainImul(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    // bool isUnsigned = node->getType().isUnsignedInt();

    if (lhs && rhs) {
        TR::VPConstraint *constraint = NULL;
        if (lhs->asIntConst() && rhs->asIntConst()) {
            // if (isUnsigned)
            //    constraint = TR::VPIntConst::create(vp,
            //    ((uint32_t)lhs->asIntConst()->getInt()*(uint32_t)rhs->asIntConst()->getInt()), isUnsigned);
            // else
            constraint = TR::VPIntConst::create(vp, lhs->asIntConst()->getInt() * rhs->asIntConst()->getInt());
        } else {
            int64_t lowerLowerLimit = (int64_t)lhs->getLowInt() * (int64_t)rhs->getLowInt();
            int64_t lowerUpperLimit = (int64_t)lhs->getLowInt() * (int64_t)rhs->getHighInt();
            int64_t upperLowerLimit = (int64_t)lhs->getHighInt() * (int64_t)rhs->getLowInt();
            int64_t upperUpperLimit = (int64_t)lhs->getHighInt() * (int64_t)rhs->getHighInt();
            int64_t tempLower1, tempLower2, lowest;
            int64_t tempHigher1, tempHigher2, highest;
            if (lowerLowerLimit < lowerUpperLimit) {
                tempLower1 = lowerLowerLimit;
                tempHigher1 = lowerUpperLimit;
            } else {
                tempLower1 = lowerUpperLimit;
                tempHigher1 = lowerLowerLimit;
            }
            if (upperLowerLimit < upperUpperLimit) {
                tempLower2 = upperLowerLimit;
                tempHigher2 = upperUpperLimit;
            } else {
                tempLower2 = upperUpperLimit;
                tempHigher2 = upperLowerLimit;
            }

            if (tempLower1 < tempLower2) {
                lowest = tempLower1;
            } else {
                lowest = tempLower2;
            }

            if (tempHigher1 > tempHigher2) {
                highest = tempHigher1;
            } else {
                highest = tempHigher2;
            }

            if (lowest < TR::getMinSigned<TR::Int32>() || highest > TR::getMaxSigned<TR::Int32>()) {
                constraint = NULL;
            } else {
                constraint = TR::VPIntRange::create(vp, (int32_t)lowest, (int32_t)highest /*, isUnsigned*/);
            }
        }
        if (constraint) {
            if (constraint->asIntConst()) {
                vp->replaceByConstant(node, constraint, lhsGlobal);
                return node;
            }

            vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
        }
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

// maxValue should either be TR::getMaxUnsigned<TR::Int32>() or TR::getMaxSigned<TR::Int32>(); the latter is used
// when this method is called with (positive) signed numbers
bool can64BitUnsignedMultiplyOverflow(uint64_t a, uint64_t b, uint64_t maxValue)
{
    // a must be the lower number
    if (a > b)
        return can64BitUnsignedMultiplyOverflow(b, a, maxValue);

    // If both numbers are >= 2^32, the product must be >= 2^64
    if (a > TR::getMaxUnsigned<TR::Int32>())
        return true;

    // If both numbers are < 2^32, the product must be < 2^64
    if (b <= TR::getMaxUnsigned<TR::Int32>())
        return false;

    // a is < 2^32; b is >= 2^32
    // If the high word of b is zero, we're multiplying two numbers both less than 2^32, which is < 2^64, so no
    // overflow. If the high word of b is non-zero, the final product x will be ((a * HIGHWORD(b)) << 32) + (a *
    // LOWWORD(b)) If we compute the highword of the above expression in a 64-bit variable, a value >= 2^32 indicates
    // overflow
    uint64_t highWord = b >> 32;
    uint64_t lowWord = b & 0xFFFFFFFF;
    uint64_t lowWordOverflow = (a * lowWord) >> 32; // Can't overflow 64 bits because a and lowWord are both < 2^32

    // The high word of a * b will be (a * highWord) + lowWordOverflow.
    // This value could possibly overflow, so check it separately.

    // If the first part is >= 2^32, a * b will overflow
    if (a * highWord > maxValue)
        return true;

    // a * highWord is < 2^32 and lowWordOverflow is < 2^32, so it's safe to add them
    // If the sum is > 2^32, a * b will overflow
    if ((a * highWord) + lowWordOverflow > maxValue)
        return true;

    return false;
}

bool can64BitSignedMultiplyOverflow(int64_t a, int64_t b)
{
    // Multiplying by 0 or 1 clearly cannot overflow
    if (a == 0 || b == 0 || a == 1 || b == 1)
        return false;

    // TR::getMinSigned<TR::Int64>() * anything but 0 or 1 will overflow
    if (a == TR::getMinSigned<TR::Int64>() || b == TR::getMinSigned<TR::Int64>())
        return true;

    // If both numbers are < 2^32, the product must be < 2^64
    if (a >= TR::getMinSigned<TR::Int32>() && a <= TR::getMaxSigned<TR::Int32>() && b >= TR::getMinSigned<TR::Int32>()
        && b <= TR::getMaxSigned<TR::Int32>())
        return false;

    // If both numbers require more than 32 bits, the product must be > 64 bits
    if ((a < TR::getMinSigned<TR::Int32>() || a > TR::getMaxSigned<TR::Int32>())
        && (b < TR::getMinSigned<TR::Int32>() || b > TR::getMaxSigned<TR::Int32>()))
        return true;

    // One number can be represented in 32 bits; the other requires 64
    // If both numbers are positive, use the unsigned code, but limit the high word
    // of the result to TR::getMaxSigned<TR::Int32>()
    if (a > 0 && b > 0)
        return can64BitUnsignedMultiplyOverflow((uint64_t)a, (uint64_t)b, TR::getMaxSigned<TR::Int32>());

    // If both are negative, multiply both by -1 and use the unsigned code
    if (a < 0 && a != TR::getMinSigned<TR::Int64>() && b < 0 && b != TR::getMinSigned<TR::Int64>())
        return can64BitUnsignedMultiplyOverflow((uint64_t)(-1 * a), (uint64_t)(-1 * b), TR::getMaxSigned<TR::Int32>());

    // Only remaining case is one is positive, one is negative, and neither is 0 nor TR::getMinSigned<TR::Int64>()
    // Treat both numbers as positive and use the unsigned code
    if (a < 0)
        return can64BitUnsignedMultiplyOverflow((uint64_t)(-1 * a), (uint64_t)b, TR::getMaxSigned<TR::Int32>());
    else if (b < 0)
        return can64BitUnsignedMultiplyOverflow((uint64_t)a, (uint64_t)(-1 * b), TR::getMaxSigned<TR::Int32>());

    return false;
}

bool can64BitMultiplyOverflow(uint64_t a, uint64_t b, bool isUnsigned)
{
    if (isUnsigned)
        return can64BitUnsignedMultiplyOverflow(a, b, TR::getMaxUnsigned<TR::Int32>());
    else
        return can64BitSignedMultiplyOverflow((int64_t)a, (int64_t)b);
}

TR::Node *constrainLmul(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;

    if (lhs && rhs) {
        TR::VPConstraint *constraint = NULL;
        if (lhs->asLongConst() && rhs->asLongConst()) {
            constraint = TR::VPLongConst::create(vp, lhs->asLongConst()->getLong() * rhs->asLongConst()->getLong());
            vp->replaceByConstant(node, constraint, lhsGlobal);
        } else {
            // If no combination overflows, it's safe to create a constraint
            if (!(can64BitMultiplyOverflow(lhs->getLowLong(), rhs->getLowLong(), node->getOpCode().isUnsigned())
                    || can64BitMultiplyOverflow(lhs->getLowLong(), rhs->getHighLong(), node->getOpCode().isUnsigned())
                    || can64BitMultiplyOverflow(lhs->getHighLong(), rhs->getLowLong(), node->getOpCode().isUnsigned())
                    || can64BitMultiplyOverflow(lhs->getHighLong(), rhs->getHighLong(),
                        node->getOpCode().isUnsigned()))) {
                int64_t lowerLowerLimit = lhs->getLowLong() * rhs->getLowLong();
                int64_t lowerUpperLimit = lhs->getLowLong() * rhs->getHighLong();
                int64_t upperLowerLimit = lhs->getHighLong() * rhs->getLowLong();
                int64_t upperUpperLimit = lhs->getHighLong() * rhs->getHighLong();
                int64_t tempLower1, tempLower2, lowest;
                int64_t tempHigher1, tempHigher2, highest;
                if (lowerLowerLimit < lowerUpperLimit) {
                    tempLower1 = lowerLowerLimit;
                    tempHigher1 = lowerUpperLimit;
                } else {
                    tempLower1 = lowerUpperLimit;
                    tempHigher1 = lowerLowerLimit;
                }
                if (upperLowerLimit < upperUpperLimit) {
                    tempLower2 = upperLowerLimit;
                    tempHigher2 = upperUpperLimit;
                } else {
                    tempLower2 = upperUpperLimit;
                    tempHigher2 = upperLowerLimit;
                }

                if (tempLower1 < tempLower2) {
                    lowest = tempLower1;
                } else {
                    lowest = tempLower2;
                }

                if (tempHigher1 > tempHigher2) {
                    highest = tempHigher1;
                } else {
                    highest = tempHigher2;
                }

                constraint = TR::VPLongRange::create(vp, lowest, highest);
            }

            if (constraint) {
                if (constraint->asLongConst()) {
                    vp->replaceByConstant(node, constraint, lhsGlobal);
                    return node;
                }

                bool didReduction = reduceLongOpToIntegerOp(vp, node, constraint);
                vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
                if (didReduction)
                    return node;
            }
        }
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

static bool isUnsafeDivide(int64_t dividend, int64_t divisor)
{
    return (dividend == TR::getMinSigned<TR::Int64>() && divisor == -1);
}

// Given the ranges for a dividend and divisor, compute the range of the result
bool constrainIntegerDivisionRange(int64_t lhsLow, int64_t lhsHigh, int64_t rhsLow, int64_t rhsHigh, int64_t rangeMin,
    int64_t rangeMax, int64_t &resultLow, int64_t &resultHigh, bool allowZeroInDivisorRange)
{
    // No matter what, division by constant 0 isn't something we'll ever simplify
    if (rhsLow == 0 && rhsHigh == 0)
        return false;

    if (!allowZeroInDivisorRange && rhsLow <= 0 && rhsHigh >= 0)
        return false;

    // The range rhsLow, rhsHigh may include 0. If it does, adjust or split the range so it doesn't include 0, since
    // integer division by 0 is meaningless and should be handled by an exception when executed. In other words, this
    // method determines the range of the result assuming the divisor is valid.
    if (rhsLow == 0)
        rhsLow = 1;

    if (rhsHigh == 0)
        rhsHigh = -1;

    resultLow = rangeMax;
    resultHigh = rangeMin;

    if (isUnsafeDivide(lhsLow, rhsLow) || isUnsafeDivide(lhsLow, rhsHigh) || isUnsafeDivide(lhsHigh, rhsLow)
        || isUnsafeDivide(lhsHigh, rhsHigh))
        return false;

    if (rhsLow < 0 && rhsHigh > 0) {
        if (isUnsafeDivide(lhsLow, -1) || isUnsafeDivide(lhsHigh, -1))
            return false;
        int64_t computed[8];
        computed[0] = lhsLow / rhsLow;
        computed[1] = lhsLow / -1;
        computed[2] = lhsLow;
        computed[3] = lhsLow / rhsHigh;

        computed[4] = lhsHigh / rhsLow;
        computed[5] = lhsHigh / -1;
        computed[6] = lhsHigh;
        computed[7] = lhsHigh / rhsHigh;

        for (int i = 0; i < 8; i++) {
            resultLow = std::min(resultLow, computed[i]);
            resultHigh = std::max(resultHigh, computed[i]);
        }
    } else {
        int64_t computed[4];
        computed[0] = lhsLow / rhsLow;
        computed[1] = lhsLow / rhsHigh;

        computed[2] = lhsHigh / rhsLow;
        computed[3] = lhsHigh / rhsHigh;

        for (int i = 0; i < 4; i++) {
            resultLow = std::min(resultLow, computed[i]);
            resultHigh = std::max(resultHigh, computed[i]);
        }
    }

    if (resultLow < rangeMin)
        resultLow = rangeMin;

    if (resultHigh > rangeMax)
        resultHigh = rangeMax;

    if (resultLow > resultHigh)
        return false; // doesn't make sense

    return true;
}

TR::Node *cloneDivForDivideByZeroCheck(OMR::ValuePropagation *vp, TR::Node *divNode)
{
    TR::Node *divCopy = TR::Node::create(divNode, divNode->getOpCodeValue(), 2);
    divCopy->setAndIncChild(0, divNode->getFirstChild());
    divCopy->setAndIncChild(1, divNode->getSecondChild());
    divCopy->incReferenceCount();
    return divCopy;
}

bool doesRangeContainZero(int64_t rangeMin, int64_t rangeMax)
{
    if (rangeMin == 0 || rangeMax == 0)
        return true;
    else if (rangeMin < 0 && rangeMax > 0)
        return true;
    else
        return false;
}

TR::Node *constrainIdiv(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    bool isUnsigned = node->getOpCode().isUnsigned();
    TR::VPConstraint *constraint = NULL;
    if (lhs && rhs) {
        if (lhs->asIntConst() && rhs->asIntConst()) {
            int32_t lhsConst = lhs->asIntConst()->getInt();
            int32_t rhsConst = rhs->asIntConst()->getInt();
            if (lhsConst == TR::getMinSigned<TR::Int32>() && rhsConst == -1 && !isUnsigned)
                constraint = TR::VPIntConst::create(vp, static_cast<int32_t>(TR::getMinSigned<TR::Int32>()));
            else if (rhsConst != 0) {
                if (isUnsigned)
                    constraint = TR::VPIntConst::create(vp, ((uint32_t)lhsConst / (uint32_t)rhsConst));
                else
                    constraint = TR::VPIntConst::create(vp, lhsConst / rhsConst);
            }
        } else {
            TR::VPIntConstraint *lhsInt = lhs->asIntConstraint();
            TR::VPIntConstraint *rhsInt = rhs->asIntConstraint();
            if (lhsInt && rhsInt) {
                int64_t lhs1, lhs2, rhs1, rhs2;
                int64_t range_min, range_max;
                if (isUnsigned) {
                    lhs1 = lhs->getUnsignedLowInt(), lhs2 = lhs->getUnsignedHighInt();
                    rhs1 = rhs->getUnsignedLowInt(), rhs2 = rhs->getUnsignedHighInt();
                    range_min = 0;
                    range_max
                        = TR::getMaxSigned<TR::Int32>(); // we don't support unsigned range constraint yet. Only support
                                                         // upto TR::getMaxSigned<TR::Int32>() for unsigned
                } else {
                    lhs1 = lhs->getLowInt(), lhs2 = lhs->getHighInt();
                    rhs1 = rhs->getLowInt(), rhs2 = rhs->getHighInt();
                    range_min = TR::getMinSigned<TR::Int32>();
                    range_max = TR::getMaxSigned<TR::Int32>();
                }
                int64_t lo, hi;

                if (constrainIntegerDivisionRange(lhs1, lhs2, rhs1, rhs2, range_min, range_max, lo, hi,
                        vp->computeDivRangeWhenDivisorCanBeZero(node)))
                    constraint = TR::VPIntRange::create(vp, (int32_t)lo, (int32_t)hi);
            }
        }
    }

    if (constraint) {
        if (constraint->asIntConst()) {
            // If constrainIntegerDivisionRange reduced the range to a constant, but the original
            // dividend contained 0 in its range, create a copy of the original div, to be anchored
            // under the original DIVCHK, so that the constant value can be optimized further,
            // but division by zero is still caught
            TR::Node *newDiv = NULL;
            if (doesRangeContainZero(rhs->getLowInt(), rhs->getHighInt()))
                newDiv = cloneDivForDivideByZeroCheck(vp, node);
            vp->replaceByConstant(node, constraint, lhsGlobal);
            if (newDiv != NULL)
                return newDiv;
            return node;
        }
        vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainLdiv(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    bool isUnsigned = node->getOpCode().isUnsigned();
    TR::Node *firstChild = node->getFirstChild();
    TR::Node *secondChild = node->getSecondChild();
    TR::VPConstraint *lhs = vp->getConstraint(firstChild, lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(secondChild, rhsGlobal);
    TR::Node *returnNode = node;

    lhsGlobal &= rhsGlobal;
    if (lhs && lhs->asLongConst() && rhs && rhs->asLongConst()) {
        TR::VPConstraint *constraint = NULL;
        int64_t lhsConst = lhs->asLongConst()->getLong();
        int64_t rhsConst = rhs->asLongConst()->getLong();
        if (lhsConst == TR::getMinSigned<TR::Int64>() && rhsConst == -1 && !isUnsigned)
            constraint = TR::VPLongConst::create(vp, TR::getMinSigned<TR::Int64>());
        else if (rhsConst != 0L) {
            if (isUnsigned)
                constraint = TR::VPLongConst::create(vp, ((uint64_t)lhsConst / (uint64_t)rhsConst));
            else
                constraint = TR::VPLongConst::create(vp, TR::Compiler->arith.longDivideLong(lhsConst, rhsConst));
        }
        if (constraint) {
            vp->replaceByConstant(node, constraint, lhsGlobal);
        }
    } else if (lhs && rhs) {
        TR::VPLongConstraint *lhsConstraint = lhs->asLongConstraint();
        TR::VPLongConstraint *rhsConstraint = rhs->asLongConstraint();

        // Try and reduce to an integer divide
        if (vp->lastTimeThrough() && !isUnsigned && lhsConstraint
            && (lhsConstraint->getLow() >= ((int64_t)TR::getMinSigned<TR::Int32>()))
            && (lhsConstraint->getHigh() <= ((int64_t)TR::getMaxSigned<TR::Int32>())) && rhsConstraint
            && (rhsConstraint->getLow() >= ((int64_t)TR::getMinSigned<TR::Int32>()))
            && (rhsConstraint->getHigh() <= ((int64_t)TR::getMaxSigned<TR::Int32>()))
            && ((lhsConstraint->getLow() > TR::getMinSigned<TR::Int32>()) || (rhsConstraint->getLow() > -1)
                || (rhsConstraint->getHigh() < -1))
            && performTransformation(vp->comp(), "%sChange node [" POINTER_PRINTF_FORMAT "] ldiv->i2l of idiv\n",
                OPT_DETAILS, node)) {
            // Change node into i2l to be used by any commoned references
            TR::Node::recreate(node, TR::i2l);
            node->setNumChildren(1);

            TR::Node *lhsNode = TR::Node::create(TR::l2i, 1, firstChild);
            TR::Node *rhsNode = TR::Node::create(TR::l2i, 1, secondChild);
            TR::Node *newIdivNode = TR::Node::create(TR::idiv, 2, lhsNode, rhsNode);
            node->setAndIncChild(0, newIdivNode);

            firstChild->recursivelyDecReferenceCount();
            secondChild->recursivelyDecReferenceCount();

            if (vp->_curTree->getNode()->getOpCodeValue() == TR::DIVCHK
                && vp->_curTree->getNode()->getFirstChild() == node) {
                // messier scenario
                // in this case, my parent is relying on me to be a divide
                //   rather than relying on me to be a long value
                //   so return the idiv node and anchor the i2l in a
                //   treetop following this divchk

                // create a treetop containing the i2l following the current tree
                TR::Node *newTreeNode = TR::Node::create(TR::treetop, 1, node);
                TR::TreeTop *newTree = TR::TreeTop::create(vp->comp(), newTreeNode);
                TR::TreeTop *prevTree = vp->_curTree;
                newTree->join(prevTree->getNextTreeTop());
                prevTree->join(newTree);

                // make sure parent has idiv as child
                returnNode = newIdivNode;
                newIdivNode->incReferenceCount();
                node->decReferenceCount();
            }
            // else parent requires the long value so returnNode should be the i2l

            // Create a constraint for the newly-created idiv node
            int64_t lhs1 = lhsConstraint->getLow(), lhs2 = lhsConstraint->getHigh();
            int64_t rhs1 = rhsConstraint->getLow(), rhs2 = rhsConstraint->getHigh();
            int64_t lo = 0, hi = 0;
            TR::VPConstraint *idivConstraint = NULL;
            if (constrainIntegerDivisionRange(lhs1, lhs2, rhs1, rhs2, TR::getMinSigned<TR::Int32>(),
                    TR::getMaxSigned<TR::Int32>(), lo, hi, vp->computeDivRangeWhenDivisorCanBeZero(newIdivNode)))
                idivConstraint = TR::VPIntRange::create(vp, (int32_t)lo, (int32_t)hi);
            if (idivConstraint) {
                // If constrainIntegerDivisionRange reduced the range to a constant, but the original
                // divisor contained 0 in its range, create a copy of the original div, to be anchored
                // under the original DIVCHK, so that the constant value can be optimized further,
                // but division by zero is still caught
                if (idivConstraint->asIntConst()) {
                    TR::Node *newDiv = NULL;
                    if (doesRangeContainZero(rhs1, rhs2))
                        newDiv = cloneDivForDivideByZeroCheck(vp, newIdivNode);
                    vp->replaceByConstant(newIdivNode, idivConstraint, lhsGlobal);
                    if (newDiv != NULL)
                        return newDiv;
                    else
                        return returnNode;
                }

                vp->addBlockOrGlobalConstraint(newIdivNode, idivConstraint, lhsGlobal);
                vp->addBlockOrGlobalConstraint(node, TR::VPLongRange::create(vp, lo, hi), lhsGlobal);
            } else {
                // Even with no constraint on idiv, the result of i2l is in int range.
                TR::VPConstraint *i2lConstraint
                    = TR::VPLongRange::create(vp, TR::getMinSigned<TR::Int32>(), TR::getMaxSigned<TR::Int32>());
                vp->addBlockOrGlobalConstraint(node, i2lConstraint, lhsGlobal);
            }
        } else if (!isUnsigned) {
            if (lhsConstraint && rhsConstraint) {
                int64_t lhs1 = lhsConstraint->getLowLong(), lhs2 = lhsConstraint->getHighLong();
                int64_t rhs1 = rhsConstraint->getLowLong(), rhs2 = rhsConstraint->getHighLong();
                int64_t lo, hi;
                if (constrainIntegerDivisionRange(lhs1, lhs2, rhs1, rhs2, TR::getMinSigned<TR::Int64>(),
                        TR::getMaxSigned<TR::Int64>(), lo, hi, vp->computeDivRangeWhenDivisorCanBeZero(node))) {
                    TR::VPConstraint *constraint = TR::VPLongRange::create(vp, lo, hi);
                    if (constraint) {
                        // If constrainIntegerDivisionRange reduced the range to a constant, but the original
                        // divisor contained 0 in its range, create a copy of the original div, to be anchored
                        // under the original DIVCHK, so that the constant value can be optimized further,
                        // but division by zero is still caught
                        if (constraint->asLongConst()) {
                            TR::Node *newDiv = NULL;
                            if (doesRangeContainZero(rhs1, rhs2))
                                newDiv = cloneDivForDivideByZeroCheck(vp, node);
                            vp->replaceByConstant(node, constraint, lhsGlobal);
                            if (NULL != newDiv)
                                return newDiv;
                            else
                                return node;
                        }

                        vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
                    }
                }
            }
        } else {
            uint64_t lhsLow = lhsConstraint->getUnsignedLowLong();
            uint64_t rhsLow = rhsConstraint->getUnsignedLowLong();
            uint64_t lhsHigh = lhsConstraint->getUnsignedHighLong();
            uint64_t rhsHigh = rhsConstraint->getUnsignedHighLong();

            if (rhsLow >= 1 && rhsLow <= rhsHigh && lhsLow <= lhsHigh) {
                // Non-negative dividend, positive divisor
                vp->addBlockOrGlobalConstraint(node, TR::VPLongRange::create(vp, lhsLow / rhsHigh, lhsHigh / rhsLow),
                    lhsGlobal);
            }
        }
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return returnNode;
}

TR::Node *removeRedundantREM(OMR::ValuePropagation *vp, TR::Node *node, TR::VPConstraint *nodeConstraint,
    TR::VPConstraint *firstChildConstraint, TR::VPConstraint *secondChildConstraint)
{
    if (!(node->getOpCode().isRem() && node->getType().isIntegral()))
        return NULL;

    int64_t nodePrecision = nodeConstraint->getPrecision();
    int64_t fcPrecision = firstChildConstraint->getPrecision();
    int64_t scPrecision = secondChildConstraint->getPrecision();

    bool powerOfTen = false;
    int64_t constValue;

    if (secondChildConstraint->asIntConst() && isPositivePowerOfTen(secondChildConstraint->getLowInt())) {
        powerOfTen = true;
        constValue = secondChildConstraint->getLowInt();
    } else if (secondChildConstraint->asLongConst() && isPositivePowerOfTen(secondChildConstraint->getLowLong())) {
        powerOfTen = true;
        constValue = secondChildConstraint->getLowLong();
    }

    if (powerOfTen) {
        bool isUnsigned = node->getOpCode().isUnsigned();
        // the max value for a remainder result is the divisor value - 1 so when the divisor is a power of ten then -1
        // this value is one decimal precision value less (e.g. 100 - 1 = 99)
        int32_t refinedPrecision = trailingZeroes((uint64_t)constValue);
        if (!isUnsigned && fcPrecision <= refinedPrecision
            && performTransformation(vp->comp(),
                "%sRemove %s [0x%p] as child %s [0x%p] prec %lld <= divisor max prec %d (value %lld)\n", OPT_DETAILS,
                node->getOpCode().getName(), node, node->getFirstChild()->getOpCode().getName(), node->getFirstChild(),
                fcPrecision, refinedPrecision, constValue)) {
            // in this case based on the child prec and the divisor constant value that the irem is not needed (999 <
            // 1000) so can remove the irem completely irem
            //    child prec=3 (i.e. max value is 999)
            //    iconst 1000
            //
            // this doesn't work for unsigned (iurem) as these ops treat the operands as unsigned so, for example, a
            // prec=1 dividend that is negative when interpreted as unsigned will actually have 10 digits of precision
            // (-1 would be 0xFFffFFff = 4,294,967,295)
            //

            // Safely replace irem/lrem with first operand, using VP removeNode that ensures
            // use/def info is preserved.
            //
            TR::Node *firstOperand = node->getFirstChild();
            firstOperand->incReferenceCount();
            vp->removeNode(node);
            return firstOperand;
        }
    }

    return NULL;
}

TR::Node *constrainIrem(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    TR::VPConstraint *constraint = NULL;
    lhsGlobal &= rhsGlobal;
    bool isUnsigned = node->getOpCode().isUnsigned();
    if (lhs && lhs->asIntConst() && rhs && rhs->asIntConst()) {
        int32_t lhsConst = lhs->asIntConst()->getInt();
        int32_t rhsConst = rhs->asIntConst()->getInt();
        if (lhsConst == TR::getMinSigned<TR::Int32>() && rhsConst == -1 && !isUnsigned)
            constraint = TR::VPIntConst::create(vp, 0);
        else if (rhsConst != 0) {
            if (isUnsigned)
                constraint = TR::VPIntConst::create(vp, ((uint32_t)lhsConst) % ((uint32_t)rhsConst));
            else
                constraint = TR::VPIntConst::create(vp, lhsConst % rhsConst);
        }
        if (constraint)
            vp->replaceByConstant(node, constraint, lhsGlobal);
    } else if (rhs && rhs->asIntConst() && lhs && lhs->asIntConstraint()) {
        int32_t lhsLow = lhs->asIntConstraint()->getLowInt();
        int32_t lhsHigh = lhs->asIntConstraint()->getHighInt();
        int32_t rhsConst = rhs->asIntConst()->getInt();
        if (!isUnsigned && rhsConst < 0)
            rhsConst *= -1;

        // Guard against division by zero
        if (rhsConst != 0) {
            rhsConst--; // If the original const was 10, the range is from 0 to 9

            if (lhsLow > 0)
                constraint = TR::VPIntRange::create(vp, 0, rhsConst);
            else if (!isUnsigned && lhsHigh < 0)
                constraint = TR::VPIntRange::create(vp, -rhsConst, 0);
            else if (!isUnsigned)
                constraint = TR::VPIntRange::create(vp, -rhsConst, rhsConst);

            if (constraint) {
                vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
            }
        }
    }

    if (constraint && lhs && lhs->asIntConstraint() && rhs && rhs->asIntConstraint()) {
        TR::Node *newNode = removeRedundantREM(vp, node, constraint, lhs, rhs);
        if (NULL != newNode)
            node = newNode;
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainLrem(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node)) {
        return node;
    }
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    TR::VPConstraint *constraint = NULL;
    lhsGlobal &= rhsGlobal;
    if (lhs && lhs->asLongConst() && rhs && rhs->asLongConst()) {
        int64_t lhsConst = lhs->asLongConst()->getLong();
        int64_t rhsConst = rhs->asLongConst()->getLong();
        if (lhsConst == TR::getMinSigned<TR::Int64>() && rhsConst == -1)
            constraint = TR::VPLongConst::create(vp, 0);
        else if (rhsConst != 0)
            constraint = TR::VPLongConst::create(vp, TR::Compiler->arith.longRemainderLong(lhsConst, rhsConst));
        if (constraint)
            vp->replaceByConstant(node, constraint, lhsGlobal);
    } else if (rhs && rhs->asLongConst() && lhs && lhs->asLongConstraint()) {
        int64_t lhsLow = lhs->asLongConstraint()->getLowLong();
        int64_t lhsHigh = lhs->asLongConstraint()->getHighLong();
        int64_t rhsConst = rhs->asLongConst()->getLong();
        if (rhsConst < 0)
            rhsConst *= -1;

        // Guard against division by zero
        if (rhsConst != 0) {
            rhsConst--; // If the original const was 10, the range is from 0 to 9

            if (lhsLow > 0)
                constraint = TR::VPLongRange::create(vp, 0, rhsConst);
            else if (lhsHigh < 0)
                constraint = TR::VPLongRange::create(vp, -rhsConst, 0);
            else
                constraint = TR::VPLongRange::create(vp, -rhsConst, rhsConst);

            if (constraint) {
                bool didReduction = reduceLongOpToIntegerOp(vp, node, constraint);
                vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);

                if (didReduction)
                    return node;
            }
        }
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    if (constraint && lhs && lhs->asLongConstraint() && rhs && rhs->asLongConstraint()) {
        TR::Node *newNode = removeRedundantREM(vp, node, constraint, lhs, rhs);
        if (NULL != newNode)
            node = newNode;
    }

    checkForNonNegativeAndOverflowProperties(vp, node);

    return node;
}

TR::Node *constrainIneg(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool isGlobal;
    TR::VPConstraint *child = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (child) {
        if (child->asIntConst()) {
            TR::VPConstraint *constraint = TR::VPIntConst::create(vp, -child->asIntConst()->getInt());
            vp->replaceByConstant(node, constraint, isGlobal);
        } else {
            int32_t high = child->getHighInt();
            int32_t low = child->getLowInt();

            TR::VPConstraint *lowConstraint = NULL;
            TR::VPConstraint *highConstraint = NULL;
            TR::VPConstraint *constraint = NULL;
            if (low == TR::getMinSigned<TR::Int32>()) {
                lowConstraint = TR::VPIntRange::create(vp, static_cast<int32_t>(TR::getMinSigned<TR::Int32>()),
                    static_cast<int32_t>(TR::getMinSigned<TR::Int32>()), TR_yes);
                low++;
            }

            if (high == TR::getMinSigned<TR::Int32>()) {
                highConstraint = TR::VPIntRange::create(vp, static_cast<int32_t>(TR::getMinSigned<TR::Int32>()),
                    static_cast<int32_t>(TR::getMinSigned<TR::Int32>()), TR_yes);
                high++;
            }

            if (!highConstraint) {
                int32_t newHigh = -low;
                int32_t newLow = -high;
                constraint = TR::VPIntRange::create(vp, newLow, newHigh, TR_yes);

                if (lowConstraint)
                    constraint = TR::VPMergedConstraints::create(vp, lowConstraint, constraint);
            } else
                constraint = highConstraint;

            if (constraint) {
                vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
            }
        }
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainLneg(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool isGlobal;
    TR::VPConstraint *child = vp->getConstraint(node->getFirstChild(), isGlobal);

    if (child) {
        if (child->asLongConst()) {
            TR::VPConstraint *constraint = TR::VPLongConst::create(vp, -child->asLongConst()->getLong());
            vp->replaceByConstant(node, constraint, isGlobal);
        } else {
            int64_t high = child->getHighLong();
            int64_t low = child->getLowLong();

            TR::VPConstraint *lowConstraint = NULL;
            TR::VPConstraint *highConstraint = NULL;
            TR::VPConstraint *constraint = NULL;
            if (low == TR::getMinSigned<TR::Int64>()) {
                lowConstraint
                    = TR::VPLongRange::create(vp, TR::getMinSigned<TR::Int64>(), TR::getMinSigned<TR::Int64>(), TR_yes);
                low++;
            }

            if (high == TR::getMinSigned<TR::Int64>()) {
                highConstraint
                    = TR::VPLongRange::create(vp, TR::getMinSigned<TR::Int64>(), TR::getMinSigned<TR::Int64>(), TR_yes);
                high++;
            }

            if (!highConstraint) {
                int64_t newHigh = -low;
                int64_t newLow = -high;
                constraint = TR::VPLongRange::create(vp, newLow, newHigh, TR_yes);

                if (lowConstraint)
                    constraint = TR::VPMergedConstraints::create(vp, lowConstraint, constraint);
            } else
                constraint = highConstraint;

            if (constraint) {
                bool didReduction = reduceLongOpToIntegerOp(vp, node, constraint);
                vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);

                if (didReduction)
                    return node;
            }
        }
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainIabs(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool isGlobal;
    TR::VPConstraint *child = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (child == NULL) {
        vp->addGlobalConstraint(node,
            TR::VPMergedConstraints::create(vp,
                TR::VPIntConst::create(vp, static_cast<int32_t>(TR::getMinSigned<TR::Int32>())),
                TR::VPIntRange::create(vp, 0, static_cast<int32_t>(TR::getMaxSigned<TR::Int32>()))));
    } else {
        int32_t low = child->getLowInt();
        int32_t high = child->getHighInt();
        if (low == high) {
            int32_t value = low;
            if (value < 0 && value != static_cast<int32_t>(TR::getMinSigned<TR::Int32>()))
                value = -value;

            TR::VPConstraint *constraint = TR::VPIntConst::create(vp, value);
            vp->replaceByConstant(node, constraint, isGlobal);
        } else {
            TR::VPConstraint *minConstraint = NULL;
            if (low == TR::getMinSigned<TR::Int32>()) {
                minConstraint = TR::VPIntConst::create(vp, low);
                low++;
            }

            if (low < 0 && high <= 0) {
                int32_t temp = low * -1;
                low = high * -1;
                high = temp;
            } else if (low < 0 && high > 0) {
                high = std::max(low * -1, high);
                low = 0;
            } else if (performTransformation(vp->comp(),
                           "%sRemoving %s [0x%p] as child %s [0x%p] is known to be positive\n", OPT_DETAILS,
                           node->getOpCode().getName(), node, node->getFirstChild()->getOpCode().getName(),
                           node->getFirstChild())) {
                // The range of values for the child is entirely positive, so remove the iabs
                return vp->replaceNode(node, node->getFirstChild(), vp->_curTree);
            }

            if (low != high || minConstraint != NULL) {
                TR::VPConstraint *constraint = TR::VPIntRange::create(vp, low, high);
                if (minConstraint != NULL)
                    constraint = TR::VPMergedConstraints::create(vp, minConstraint, constraint);
                vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
            } else {
                TR::VPConstraint *constraint = TR::VPIntConst::create(vp, low);
                vp->replaceByConstant(node, constraint, isGlobal);
            }
        }
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainLabs(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool isGlobal;
    TR::VPConstraint *child = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (child == NULL) {
        vp->addGlobalConstraint(node,
            TR::VPMergedConstraints::create(vp, TR::VPLongConst::create(vp, TR::getMinSigned<TR::Int64>()),
                TR::VPLongRange::create(vp, 0, TR::getMaxSigned<TR::Int64>())));
    } else {
        int64_t low = child->getLowLong();
        int64_t high = child->getHighLong();
        if (low == high) {
            int64_t value = low;
            if (value < 0 && value != static_cast<int64_t>(TR::getMinSigned<TR::Int64>()))
                value = -value;

            TR::VPConstraint *constraint = TR::VPLongConst::create(vp, value);
            vp->replaceByConstant(node, constraint, isGlobal);
        } else {
            TR::VPConstraint *minConstraint = NULL;
            if (low == TR::getMinSigned<TR::Int64>()) {
                minConstraint = TR::VPLongConst::create(vp, low);
                low++;
            }

            if (low < 0 && high <= 0) {
                int64_t temp = low * -1;
                low = high * -1;
                high = temp;
            } else if (low < 0 && high > 0) {
                high = std::max(low * -1, high);
                low = 0;
            } else if (performTransformation(vp->comp(),
                           "%sRemoving %s [0x%p] as child %s [0x%p] is known to be positive\n", OPT_DETAILS,
                           node->getOpCode().getName(), node, node->getFirstChild()->getOpCode().getName(),
                           node->getFirstChild())) {
                // The range of values for the child is entirely positive, so remove the labs
                return vp->replaceNode(node, node->getFirstChild(), vp->_curTree);
            }

            if (low != high || minConstraint != NULL) {
                TR::VPConstraint *constraint = TR::VPLongRange::create(vp, low, high);
                if (minConstraint != NULL)
                    constraint = TR::VPMergedConstraints::create(vp, minConstraint, constraint);
                bool didReduction = reduceLongOpToIntegerOp(vp, node, constraint);
                vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);

                if (didReduction)
                    return node;
            } else {
                TR::VPConstraint *constraint = TR::VPLongConst::create(vp, low);
                vp->replaceByConstant(node, constraint, isGlobal);
            }
        }
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainIshl(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    // bool isUnsigned = node->getType().isUnsignedInt();
    if (lhs && lhs->asIntConst() && rhs && rhs->asIntConst()) {
        int32_t lhsConst = lhs->asIntConst()->getInt();
        int32_t rhsConst = rhs->asIntConst()->getInt();
        TR::VPConstraint *constraint = TR::VPIntConst::create(vp, lhsConst << (rhsConst & 0x01F) /*, isUnsigned*/);
        vp->replaceByConstant(node, constraint, lhsGlobal);
    }

    // Replace shift of constant zero with constant zero
    if (lhs && lhs->asIntConst() && lhs->asIntConst()->getInt() == 0) {
        vp->replaceByConstant(node, lhs, lhsGlobal);
        return node;
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainLshl(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    if (lhs && lhs->asLongConst() && rhs && rhs->asLongConst()) {
        int64_t lhsConst = lhs->asLongConst()->getLong();
        int64_t rhsConst = rhs->asLongConst()->getLong();
        TR::VPConstraint *constraint = TR::VPLongConst::create(vp, lhsConst << (rhsConst & 0x03F));
        vp->replaceByConstant(node, constraint, lhsGlobal);
    }

    // Replace shift of constant zero with constant zero
    if (lhs && lhs->asLongConst() && lhs->asLongConst()->getLong() == 0) {
        vp->replaceByConstant(node, lhs, lhsGlobal);
        return node;
    } else if (lhs && lhs->asLongConst() && lhs->asLongConst()->getLong() == 1) {
        TR::VPConstraint *constraint
            = TR::VPLongRange::create(vp, TR::getMinSigned<TR::Int64>(), TR::getMaxSigned<TR::Int64>(), true);
        vp->addBlockConstraint(node, constraint);
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

static TR::Node *distributeShift(OMR::ValuePropagation *vp, TR::Node *node, int32_t shiftAmount,
    TR::VPConstraint *&constraint)
{
    // FIXME: temporarily disabled
    //
    return NULL;
    // check if the distributive property can be applied
    // (a + b) >> amount ==> a >> amount or b >> amount if
    // it can be proven that the a (or b) >= 2^amt
    //
    // ishr                        ishr
    //    iadd             ==>        iload a
    //       iload a                  iconst amt
    //       iload b
    //    iconst amt
    //
    TR::Node *replaceNode = NULL;

    /// traceMsg(vp->comp(), "attempting to distributeShift on node [%p]\n", node);

    TR::ILOpCode &nodeOp = node->getFirstChild()->getOpCode();
    if ((nodeOp.isAdd() || nodeOp.isSub()) && node->getFirstChild()->cannotOverflow()) {
        TR::Node *lhsGrandChild = node->getFirstChild()->getFirstChild();
        TR::Node *rhsGrandChild = node->getFirstChild()->getSecondChild();
        bool isGlobal;
        TR::VPConstraint *lhsGConstraint = vp->getConstraint(lhsGrandChild, isGlobal);
        TR::VPConstraint *rhsGConstraint = vp->getConstraint(rhsGrandChild, isGlobal);
        TR::VPConstraint *c = NULL;
        // check for commutativity
        //
        if (lhsGConstraint && !nodeOp.isSub()) {
            int32_t low = lhsGConstraint->getLowInt();
            int32_t high = lhsGConstraint->getHighInt();
            if ((low >> shiftAmount == 0) && (high >> shiftAmount == 0)) {
                replaceNode = rhsGrandChild;
                c = rhsGConstraint;
            }
        } else if (rhsGConstraint) {
            int32_t low = rhsGConstraint->getLowInt();
            int32_t high = rhsGConstraint->getHighInt();
            if ((low >> shiftAmount == 0) && (high >> shiftAmount == 0)) {
                replaceNode = lhsGrandChild;
                c = lhsGConstraint;
            }
        }

        if (vp->trace())
            traceMsg(vp->comp(), "found replaceNode is [%p]\n", replaceNode);

        if (replaceNode) {
            int64_t pow2Val = int64_t(1) << shiftAmount;
            TR::VPConstraint *lhsORrhs = NULL;
            if (replaceNode == rhsGrandChild) // lhs is the redundant expr
                lhsORrhs = rhsGConstraint;
            else
                lhsORrhs = lhsGConstraint;

            if (lhsORrhs) {
                int32_t low = lhsORrhs->getLowInt();
                int32_t high = lhsORrhs->getHighInt();
                if ((low <= 0) && (((int64_t)(high + 1) & (pow2Val - 1))) == 0) {
                    if (vp->trace())
                        traceMsg(vp->comp(), "found opportunity to distribute shift in node [%p] replaceNode [%p]\n",
                            node, replaceNode);
                } else {
                    if (vp->trace())
                        traceMsg(vp->comp(),
                            "failed additive expr constraint is not within range, node [%p] replaceNode [%p]\n", node,
                            replaceNode);
                    replaceNode = NULL;
                }
            } else {
                if (vp->trace())
                    traceMsg(vp->comp(), "failed no constraint found, node [%p] replaceNode [%p]\n", node, replaceNode);
                replaceNode = NULL;
            }
        }

        if (replaceNode
            && performTransformation(vp->comp(), "%sDistributing shift across add/sub [%p]\n", OPT_DETAILS,
                node->getFirstChild())) {
            if (replaceNode->getReferenceCount() > 1)
                replaceNode = replaceNode->duplicateTree();

            node->getFirstChild()->recursivelyDecReferenceCount();
            node->setAndIncChild(0, replaceNode);
            if (c) {
                if (node->getOpCodeValue() == TR::iushr) {
                    if (c->getLowInt() >= 0)
                        constraint = TR::VPIntRange::create(vp, ((uint32_t)c->getLowInt()) >> shiftAmount,
                            ((uint32_t)c->getHighInt()) >> shiftAmount);
                    else if (c->getHighInt() < 0)
                        constraint = TR::VPIntRange::create(vp, ((uint32_t)c->getHighInt()) >> shiftAmount,
                            ((uint32_t)c->getLowInt()) >> shiftAmount);
                    // this path is probably never taken for unsigned
                    else {
                        if (shiftAmount > 0)
                            constraint = TR::VPIntRange::create(vp, 0, (uint32_t)0xffffffff >> shiftAmount);
                        else
                            constraint
                                = TR::VPIntRange::create(vp, 0, static_cast<int32_t>(TR::getMaxSigned<TR::Int32>()));
                    }
                } else
                    constraint
                        = TR::VPIntRange::create(vp, c->getLowInt() >> shiftAmount, c->getHighInt() >> shiftAmount);
            }
        }
    }
    return replaceNode;
}

TR::Node *constrainIshr(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;

    // Replace shift of constant zero with constant zero
    if (lhs && lhs->asIntConst() && lhs->asIntConst()->getInt() == 0) {
        vp->replaceByConstant(node, lhs, lhsGlobal);
        return node;
    }

    if (rhs && rhs->asIntConst()) {
        int32_t rhsConst = rhs->asIntConst()->getInt();
        int32_t shiftAmount = rhsConst & 0x01F;

        int32_t low, high;
        if (lhs) {
            low = lhs->getLowInt();
            high = lhs->getHighInt();
        } else {
            low = static_cast<int32_t>(TR::getMinSigned<TR::Int32>());
            high = static_cast<int32_t>(TR::getMaxSigned<TR::Int32>());
        }

        TR::VPConstraint *constraint = TR::VPIntRange::create(vp, low >> shiftAmount, high >> shiftAmount);

        if (constraint && !constraint->asIntConst())
            distributeShift(vp, node, shiftAmount, constraint);

        if (constraint) {
            if (constraint->asIntConst()) {
                vp->replaceByConstant(node, constraint, lhsGlobal);
                return node;
            }
            vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
        }
    }
    checkForNonNegativeAndOverflowProperties(vp, node);

    TR::Node *firstChild = node->getFirstChild();
    if (firstChild->isNonNegative() /*&& firstChild->getType().isSignedInt()*/ && vp->lastTimeThrough()
        && performTransformation(vp->comp(), "%sChange node [" POINTER_PRINTF_FORMAT "] ishr->iushr\n", OPT_DETAILS,
            node)) {
        TR::Node::recreate(node, TR::iushr);
        // TR::Node *lhs = node->getFirstChild();
        // TR::Node *rhs = node->getSecondChild();
        // TR::Node::recreate(node, TR::iu2i);
        // node->setNumChildren(1);
        // TR::Node *newIushrNode = TR::Node::create(vp->comp(), TR::iushr, 2, lhs, rhs);
        // node->setAndIncChild(0, newIushrNode);
        // lhs->decReferenceCount();
        // rhs->decReferenceCount();
    }

    // this handler is not called for unsigned shifts
    // #ifdef DEBUG
    // else if(!firstChild->getType().isSignedInt()) dumpOptDetails(vp->comp(), "FIXME: Need to support ishr opt for
    // Unsigned datatypes!\n");
    // FIXME
    // #endif
    return node;
}

TR::Node *constrainLshr(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;

    bool longShr = node->getOpCode().isLong();

    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;

    // Replace shift of constant zero with constant zero
    if (lhs && lhs->asLongConst() && lhs->asLongConst()->getLong() == 0) {
        vp->replaceByConstant(node, lhs, lhsGlobal);
        return node;
    }

    if (rhs && rhs->asIntConst()) {
        int32_t rhsConst = rhs->asIntConst()->getInt();
        int32_t shiftAmount = rhsConst & 0x03F;

        int64_t low, high;
        if (lhs) {
            low = lhs->getLowLong();
            high = lhs->getHighLong();
        } else {
            low = TR::getMinSigned<TR::Int64>();
            high = TR::getMaxSigned<TR::Int64>();
        }

        TR::VPConstraint *constraint = TR::VPLongRange::create(vp, low >> shiftAmount, high >> shiftAmount);
        if (constraint) {
            if (constraint->asLongConst()) {
                vp->replaceByConstant(node, constraint, lhsGlobal);
                return node;
            }

            bool didReduction = false;
            if (longShr) {
                TR::Node *secondChild = node->getSecondChild();
                didReduction = reduceLongOpToIntegerOp(vp, node, constraint);
                // If the shift amount is between 32 and 63, inclusive, then if we convert the node to an integer shift
                // we must reduce this shift value. Integer shifts are masked by 0x1f in java, and if the first child is
                // integer constrained, a shift by an amount in this range is effectively just a shift by 31.
                if (didReduction && shiftAmount >= 32) {
                    secondChild->decReferenceCount();
                    node->getFirstChild()->setAndIncChild(1, TR::Node::create(node, TR::iconst, 0, 31));
                }
            }

            vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);

            // If we reduced long to integer, node is now an i2l; not safe to do any further addition-related things
            // with node
            if (didReduction)
                return node;
        }
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainIushr(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    // check the type of the firstchild
    // since ishr could be converted to iushr
    // for signed types
    // bool isUnsigned = node->getFirstChild()->getType().isUnsignedInt();
    // iushr could feed into a store for example, so check that
    //
    TR::Node *parent = vp->getCurrentParent();
    // if (parent && parent->getType().isUnsignedInt())
    //    isUnsigned = true;

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;

    // Replace shift of constant zero with constant zero
    if (lhs && lhs->asIntConst() && lhs->asIntConst()->getInt() == 0) {
        vp->replaceByConstant(node, lhs, lhsGlobal);
        return node;
    }

    if (rhs && rhs->asIntConst()) {
        int32_t rhsConst = rhs->asIntConst()->getInt();
        int32_t shiftAmount = rhsConst & 0x01F;
        if (shiftAmount) {
            node->setIsNonNegative(true);
        }

        bool lhsGlobal;
        TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
        lhsGlobal &= rhsGlobal;

        int32_t low, high;
        if (lhs) {
            low = lhs->getLowInt();
            high = lhs->getHighInt();
        } else {
            // if (isUnsigned)
            //    {
            //    low = TR::getMinUnsigned<TR::Int32>();
            //    high = TR::getMaxUnsigned<TR::Int32>();
            //    }
            // else
            //    {
            low = static_cast<int32_t>(TR::getMinSigned<TR::Int32>());
            high = static_cast<int32_t>(TR::getMaxSigned<TR::Int32>());
            //   }
        }

        TR::VPConstraint *constraint = NULL;
        if (low == high)
            constraint = TR::VPIntConst::create(vp, ((uint32_t)low) >> shiftAmount);
        else if (low >= 0 || high < 0)
            constraint = TR::VPIntRange::create(vp, ((uint32_t)low) >> shiftAmount, ((uint32_t)high) >> shiftAmount);
        // this path is probably never taken for unsigned
        else {
            if (shiftAmount > 0)
                constraint = TR::VPIntRange::create(vp, 0, (uint32_t)0xffffffff >> shiftAmount);
            // if shiftAmount ==0 range should remain the same
            else {
                constraint = TR::VPIntRange::create(vp, low, high);
            }
        }

        if (constraint && !constraint->asIntConst())
            distributeShift(vp, node, shiftAmount, constraint);

        if (constraint) {
            if (constraint->asIntConst()) {
                vp->replaceByConstant(node, constraint, lhsGlobal);
                return node;
            }

            vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
        }
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainLushr(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;

    // Replace shift of constant zero with constant zero
    if (lhs && lhs->asLongConst() && lhs->asLongConst()->getLong() == 0) {
        vp->replaceByConstant(node, lhs, lhsGlobal);
        return node;
    }

    if (rhs && rhs->asIntConst()) {
        int32_t rhsConst = rhs->asIntConst()->getInt();
        int32_t shiftAmount = rhsConst & 0x03F;
        if (shiftAmount) {
            node->setIsNonNegative(true);
        }

        bool lhsGlobal;
        TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
        lhsGlobal &= rhsGlobal;

        int64_t low, high;
        if (lhs) {
            low = lhs->getLowLong();
            high = lhs->getHighLong();
        } else {
            low = TR::getMinSigned<TR::Int64>();
            high = TR::getMaxSigned<TR::Int64>();
        }

        TR::VPConstraint *constraint = NULL;
        if (low == high)
            constraint = TR::VPLongConst::create(vp, ((uint64_t)low) >> shiftAmount);
        else if (low >= 0 || high < 0)
            constraint = TR::VPLongRange::create(vp, ((uint64_t)low) >> shiftAmount, ((uint64_t)high) >> shiftAmount);
        else {
            if (shiftAmount > 0)
                constraint = TR::VPLongRange::create(vp, 0, ((uint64_t)-1L) >> shiftAmount);
            else
                constraint = TR::VPLongRange::create(vp, low, high);
        }

        if (constraint) {
            if (constraint->asLongConst()) {
                vp->replaceByConstant(node, constraint, lhsGlobal);
                return node;
            }

            vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
        }
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainIand(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    bool signBitsMaskedOff = 0;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    TR::VPConstraint *constraint = NULL;
    // bool isUnsigned = node->getType().isUnsignedInt();

    bool canBeRemoved = false;

    if (rhs && rhs->asIntConst()) {
        int32_t mask = rhs->asIntConst()->getInt();
        signBitsMaskedOff = leadingZeroes(mask) != 0;

        // take opportunity to remove if unneeded
        if (255 == mask && lhs) // loading from byte
        {
            if (lhs->asIntRange()) {
                TR::VPIntRange *range = lhs->asIntRange();
                int32_t low = range->getLowInt();
                int32_t high = range->getHighInt();
                // bool withinRange = isUnsigned ? ((uint32_t)high <= 255) : (high <= 255);
                if (low >= 0 && high <= 255
                    && performTransformation(vp->comp(), "%sRemoving node [%p] %s\n", OPT_DETAILS, node,
                        node->getOpCode().getName())) {
                    if (node->getReferenceCount() > 1)
                        node->getFirstChild()->incReferenceCount();

                    node->decReferenceCount();
                    // fix the const child's reference count
                    if (node->getReferenceCount() == 0)
                        node->getSecondChild()->decReferenceCount();

                    // printf("remove andi 255 opportunity found in %s\n", vp->comp()->signature());
                    //  invalidate its use def info
                    int32_t useIndex = node->getUseDefIndex();
                    TR_UseDefInfo *info = vp->optimizer()->getUseDefInfo();
                    if (info && (info->isDefIndex(useIndex) || info->isUseIndex(useIndex))) {
                        if (info->getNode(useIndex) == node)
                            info->clearNode(useIndex);
                    }

                    node->setUseDefIndex(0);

                    return node->getFirstChild(); // assume const is right child
                }
            }
        }

        if ((mask & 0x80000000) == 0) {
            node->setIsNonNegative(true);
        }
        if (mask == 0) {
            constraint = TR::VPIntConst::create(vp, 0 /*, isUnsigned*/);
        } else if (lhs && lhs->asIntConst()) {
            // if (isUnsigned)
            //    constraint = TR::VPIntConst::create(vp, ((uint32_t)lhs->asIntConst()->getInt() & (uint32_t)mask),
            //    isUnsigned);
            // else
            constraint = TR::VPIntConst::create(vp, lhs->asIntConst()->getInt() & mask);
        } else {
            // Below code will fold away isArray test laid down by
            // loop versioner to check if the type of the object we are getting
            // arraylength from is an array type. It should also work in
            // general for isArray tests in a user program
            //
            if (rhs && rhs->asIntConst()) {
                TR::Node *firstChild = node->getFirstChild();

                // On 64bit platform, the flag is 64 bits long and is casted to int
                if (firstChild->getOpCodeValue() == TR::l2i)
                    firstChild = firstChild->getChild(0);

                if ((firstChild->getOpCodeValue() == TR::iloadi || firstChild->getOpCodeValue() == TR::lloadi)
                    && (firstChild->getSymbolReference()
                        == vp->comp()->getSymRefTab()->findClassDepthAndFlagsSymbolRef())
                    && (rhs->getLowInt() == TR::Compiler->cls.flagValueForArrayCheck(vp->comp()))) {
                    if (vp->trace())
                        traceMsg(vp->comp(), "Found isArray test on node %p\n", node);

                    TR::Node *classNode = firstChild->getFirstChild();

                    bool classConstraintIsGlobal;
                    TR::VPConstraint *classConstraint = vp->getConstraint(classNode, classConstraintIsGlobal);

                    const char *hitOrMiss = "miss";
                    if (classConstraint != NULL && classConstraint->isJ9ClassObject() == TR_yes
                        && classConstraint->getClassType() != NULL
                        && classConstraint->getClassType()->isArray() != TR_maybe) {
                        hitOrMiss = "hit";

                        if (classConstraint->getClassType()->isArray() == TR_yes)
                            constraint = TR::VPIntConst::create(vp, rhs->asIntConst()->getLowInt());
                        else
                            constraint = TR::VPIntConst::create(vp, 0);
                    }

                    TR::DebugCounter::incStaticDebugCounter(vp->comp(),
                        TR::DebugCounter::debugCounterName(vp->comp(), "isArrayTest/%s/(%s)/%s", hitOrMiss,
                            vp->comp()->signature(), vp->comp()->getHotnessName(vp->comp()->getMethodHotness())));
                }
            }

            // look for mod by power of 2 case
            if (!constraint) {
                if (mask != 0xffffffff && isNonNegativePowerOf2(mask + 1)) {
                    int32_t low = 0;
                    int32_t high = mask;
                    //                if (isUnsigned)
                    //                   {
                    //                   if (lhs && (uint32_t)lhs->getLowInt() >= 0 &&
                    //                               (uint32_t)lhs->getHighInt() <= mask)
                    //                      {
                    //                      if ((uint32_t)lhs->getLowInt() > 0)
                    //                        low = lhs->getLowInt();
                    //                      if ((uint32_t)lhs->getHighInt() >= 0)
                    //                         high = lhs->getHighInt();
                    //                      }
                    //                   }
                    //                else
                    if (lhs && lhs->getLowInt() >= 0 && lhs->getHighInt() <= mask) {
                        canBeRemoved = true;
                        if (vp->trace())
                            traceMsg(vp->comp(), "Removing redundant iand [%p] due to range\n", node);

                        if (lhs->getLowInt() > 0)
                            low = lhs->getLowInt();
                        if (lhs->getHighInt() >= 0)
                            high = lhs->getHighInt();
                    }
                    constraint = TR::VPIntRange::create(vp, low, high /*, isUnsigned*/);
                } else if ((mask & 0x80000000) == 0) {
                    constraint = TR::VPIntRange::create(vp, 0, mask /*, isUnsigned*/);
                } else {
                    // if (isUnsigned)
                    //    constraint = TR::VPIntRange::create(vp, TR::getMinUnsigned<TR::Int32>(), mask, isUnsigned);
                    // else
                    constraint = TR::VPIntRange::create(vp, static_cast<int32_t>(TR::getMinSigned<TR::Int32>()),
                        mask & static_cast<int32_t>(TR::getMaxSigned<TR::Int32>()));
                }
            }
        }
    } else {
        if (lhs && lhs->getLowInt() > 0)
            constraint = TR::VPIntRange::create(vp, 0, lhs->getHighInt() /*, isUnsigned*/);
    }

    // Evaluate to zero optimization: lhs will atleast contain the same number of trailng zeros as lrhs.
    // When lhs & rhs, and rhs < 0b1000..(number of trailing zeros in lhs), then the whole iand node evaluates to a
    // constant zero. The Opt doesn't kick in when rhs is negative, because of MSB being 1.

    bool lrhsGlobal;
    TR::VPConstraint *lrhs = NULL;
    if (node->getFirstChild()->getNumChildren() > 1)
        lrhs = vp->getConstraint(node->getFirstChild()->getSecondChild(), lrhsGlobal);
    if (rhs && lrhs && lrhs->asIntConst()) {
        int32_t mask = lrhs->asIntConst()->getInt();
        int32_t shift = 0;
        if (rhs->asIntRange()) {
            TR::VPIntRange *range = rhs->asIntRange();
            int32_t low = range->getLowInt();
            int32_t high = range->getHighInt();
            if (node->getFirstChild()->getOpCodeValue() == TR::imul && ((high & 0x80000000) == 0)
                && ((low & 0x80000000) == 0)) {
                while (!(mask & 0x1)) {
                    shift++;
                    mask >>= 1;
                }
                if (high < 1 << shift)
                    constraint = TR::VPIntConst::create(vp, 0);
            } else if (node->getFirstChild()->getOpCodeValue() == TR::ishl && ((high & 0x80000000) == 0)
                && ((low & 0x80000000) == 0)) {
                if (high < 1 << mask)
                    constraint = TR::VPIntConst::create(vp, 0);
            }
        } else if (rhs->asIntConst()) {
            int32_t iandMask = rhs->asIntConst()->getInt();
            if (node->getFirstChild()->getOpCodeValue() == TR::imul && ((iandMask & 0x80000000) == 0)) {
                while (!(mask & 0x1)) {
                    shift++;
                    mask >>= 1;
                }
                if (iandMask < 1 << shift)
                    constraint = TR::VPIntConst::create(vp, 0);
            } else if (node->getFirstChild()->getOpCodeValue() == TR::ishl && ((iandMask & 0x80000000) == 0)) {
                if (iandMask < 1 << mask)
                    constraint = TR::VPIntConst::create(vp, 0);
            }
        }
    }

    if (canBeRemoved
        && performTransformation(vp->comp(), "%sRemoving redundant node [%p] %s\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        TR::Node *firstChild = node->getFirstChild();
        firstChild->incReferenceCount();
        vp->removeNode(node, false);
        node = firstChild;
        return node;
    }

    // need this to catch mask of 255 or shortint
    // FIXME: disabled for unsigned
    if (!constraint && (lhs || rhs) /* && !isUnsigned*/) {
        int32_t lhsHigh = static_cast<int32_t>(TR::getMaxSigned<TR::Int32>()),
                rhsHigh = static_cast<int32_t>(TR::getMaxSigned<TR::Int32>());
        int32_t lhsLow = static_cast<int32_t>(TR::getMinSigned<TR::Int32>()),
                rhsLow = static_cast<int32_t>(TR::getMinSigned<TR::Int32>());
        int32_t high, low;

        if (lhs && lhs->asIntRange()) {
            TR::VPIntRange *range = lhs->asIntRange();
            lhsLow = range->getLowInt();
            lhsHigh = range->getHighInt();
        }

        if (rhs && rhs->asIntRange()) {
            TR::VPIntRange *range = rhs->asIntRange();
            rhsLow = range->getLowInt();
            rhsHigh = range->getHighInt();
        }

        if (rhsLow >= 0 || lhsLow >= 0) {
            if (rhsLow < 0) // punt
            {
                low = lhsLow;
                high = lhsHigh;
            } else if (lhsLow < 0) {
                low = rhsLow;
                high = rhsHigh;
            } else {
                low = rhsLow < lhsLow ? rhsLow : lhsLow;
                high = rhsHigh < lhsHigh ? rhsHigh : lhsHigh; // intersection, so will be smaller
            }
            if (low > 0)
                low = 0;

            constraint = TR::VPIntRange::create(vp, low, high);

            // printf("new constraint opportunity %d-%d (%x to %x)found in %s\n",
            // low,high,low,high,vp->comp()->signature());
        }
    }

    if (constraint) {
        if (constraint->asIntConst()) {
            vp->replaceByConstant(node, constraint, lhsGlobal);
            return node;
        }
        vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
    }

    checkForNonNegativeAndOverflowProperties(vp, node);

    TR::Node *firstChild = node->getFirstChild();

    // ishr will propogate the sign bit.  If we mask off any propogation
    // then can replace with unsigned shift.  If we mask off more high bits
    // than are shifted, we can do it.
    if(TR::ishr == firstChild->getOpCodeValue() &&
      firstChild->getReferenceCount() < 2 /*&&
                                            firstChild->getFirstChild()->getType().isSignedInt()*/)  // FIXME: handle unsigned when supported
      {
        bool doNotShiftHighBit = false;
        bool isGlobal;
        TR::Node *shiftAmount = firstChild->getSecondChild();
        TR::VPConstraint *shiftConstraint = vp->getConstraint(shiftAmount, isGlobal);
        int32_t bitsShifted = 32;

        if (shiftConstraint && shiftConstraint->asIntConst()) {
            bitsShifted = shiftConstraint->asIntConst()->getInt();
        } else if (shiftConstraint && shiftConstraint->asIntRange()
            && shiftConstraint->asIntRange()->getLowInt() >= 0) {
            bitsShifted = shiftConstraint->asIntRange()->getHighInt();
        }

        if ((bitsShifted < (int32_t)signBitsMaskedOff) && vp->lastTimeThrough()
            && performTransformation(vp->comp(),
                "%s Node [" POINTER_PRINTF_FORMAT "]: ishr -> iushr (parent ignores sign bits)\n", OPT_DETAILS,
                firstChild)) {
            TR::Node::recreate(firstChild, TR::iushr);
            // TR::Node *lhs = firstChild->getFirstChild();
            // TR::Node *rhs = firstChild->getSecondChild();
            // TR::Node::recreate(firstChild, TR::iu2i);
            // firstChild->setNumChildren(1);
            // TR::Node *newIushrNode = TR::Node::create(vp->comp(), TR::iushr, 2, lhs, rhs);
            // firstChild->setAndIncChild(0, newIushrNode);
            // lhs->decReferenceCount();
            // rhs->decReferenceCount();
        }
    }

    return node;
}

TR::Node *constrainLand(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    TR::VPConstraint *constraint = NULL;
    if (rhs && rhs->asLongConst()) {
        int64_t mask = rhs->asLongConst()->getLong();
        if (mask >= 0L) {
            node->setIsNonNegative(true);
        }
        if (mask == 0L) {
            constraint = TR::VPLongConst::create(vp, 0);
        } else if (lhs && lhs->asLongConst()) {
            constraint = TR::VPLongConst::create(vp, lhs->asLongConst()->getLong() & mask);
        } else {
            // look for mod by power of 2 case
            if (mask != -1L && isNonNegativePowerOf2(mask + 1)) {
                int64_t low = 0;
                int64_t high = mask;
                if (lhs && lhs->getLowLong() >= 0 && lhs->getHighLong() <= mask) {
                    if (lhs->getLowLong() > 0)
                        low = lhs->getLowLong();
                    if (lhs->getHighLong() >= 0)
                        high = lhs->getHighLong();
                }
                constraint = TR::VPLongRange::create(vp, low, high);
            } else if ((mask & TR::getMinSigned<TR::Int64>()) == 0L) {
                constraint = TR::VPLongRange::create(vp, 0L, mask);
            } else {
                constraint
                    = TR::VPLongRange::create(vp, TR::getMinSigned<TR::Int64>(), mask & TR::getMaxSigned<TR::Int64>());
            }
        }
    } else if (lhs && lhs->getLowLong() > 0) {
        constraint = TR::VPLongRange::create(vp, 0L, lhs->getHighLong());
    }
    if (constraint) {
        if (constraint->asLongConst()) {
            vp->replaceByConstant(node, constraint, lhsGlobal);
            return node;
        }
        vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainIor(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    // bool isUnsigned = node->getType().isUnsignedInt();

    if (lhs && lhs->asIntConst() && rhs && rhs->asIntConst()) {
        int32_t lhsConst = lhs->asIntConst()->getInt();
        int32_t rhsConst = rhs->asIntConst()->getInt();
        TR::VPConstraint *constraint = NULL;
        // if (isUnsigned)
        //    constraint = TR::VPIntConst::create(vp, ((uint32_t)lhsConst | (uint32_t)rhsConst), isUnsigned);
        // else
        constraint = TR::VPIntConst::create(vp, lhsConst | rhsConst /*, isUnsigned*/);
        vp->replaceByConstant(node, constraint, lhsGlobal);
    }

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainLor(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    if (lhs && lhs->asLongConst() && rhs && rhs->asLongConst()) {
        int64_t lhsConst = lhs->asLongConst()->getLong();
        int64_t rhsConst = rhs->asLongConst()->getLong();
        TR::VPConstraint *constraint = TR::VPLongConst::create(vp, lhsConst | rhsConst);
        vp->replaceByConstant(node, constraint, lhsGlobal);
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainIxor(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    // bool isUnsigned = node->getType().isUnsignedInt();

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    TR::VPConstraint *constraint = NULL;
    if (lhs && rhs && rhs->asIntConst()) {
        int32_t rhsConst = rhs->asIntConst()->getInt();
        if (lhs->asIntConst()) {
            int32_t lhsConst = lhs->asIntConst()->getInt();
            // if (isUnsigned)
            //    constraint = TR::VPIntConst::create(vp, ((uint32_t)lhsConst ^ (uint32_t)rhsConst), isUnsigned);
            // else
            constraint = TR::VPIntConst::create(vp, lhsConst ^ rhsConst /*, isUnsigned*/);
            vp->replaceByConstant(node, constraint, lhsGlobal);
            return node;
        }
        if (rhsConst == 1 && lhs->asIntRange()) {
            // Special common case of xor a range (usually 0->1) with 1
            //
            constraint = TR::VPIntRange::create(vp, (lhs->getLowInt() & ~1), (lhs->getHighInt() | 1) /*, isUnsigned*/);
            if (constraint) {
                vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
            }
        }
    }

    // Look for nested boolean negations
    //
    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainLxor(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;
    if (lhs && lhs->asLongConst() && rhs && rhs->asLongConst()) {
        int64_t lhsConst = lhs->asLongConst()->getLong();
        int64_t rhsConst = rhs->asLongConst()->getLong();
        TR::VPConstraint *constraint = TR::VPLongConst::create(vp, lhsConst ^ rhsConst);
        vp->replaceByConstant(node, constraint, lhsGlobal);
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

static TR::Node *constrainWidenToLong(OMR::ValuePropagation *vp, TR::Node *node, int64_t low, int64_t high,
    bool isUnsigned)
{
    if (findConstant(vp, node))
        return node;
    constrainChildren(vp, node);
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (constraint) {
        if (constraint->asIntConstraint()) {
            if (isUnsigned) {
                if (constraint->getLowInt() > 0)
                    low = constraint->getLowInt();
                if (constraint->getLowInt() >= 0 && constraint->getHighInt() < high)
                    high = constraint->getHighInt();
            } else {
                if (constraint->getLowInt() > low)
                    low = constraint->getLowInt();
                if (constraint->getHighInt() < high)
                    high = constraint->getHighInt();
            }
        } else if (constraint->asShortConstraint()) {
            if (isUnsigned) {
                if (constraint->getLowShort() > 0)
                    low = constraint->getLowShort();
                if (constraint->getLowShort() > 0 && constraint->getHighShort() < high)
                    high = constraint->getHighShort();
            } else {
                if (constraint->getLowShort() > low)
                    low = constraint->getLowShort();
                if (constraint->getHighShort() < high)
                    high = constraint->getHighShort();
            }
        }
    }

    if (low <= high) {
        // Make sure the constraint is not trivial
        //
        constraint = TR::VPLongRange::create(vp, low, high);
        if (constraint) {
            vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
        }
        if (low >= 0)
            node->setIsNonNegative(true);
    }

    if (vp->isHighWordZero(node))
        node->setIsHighWordZero(true);

    checkForNonNegativeAndOverflowProperties(vp, node);
    return node;
}

TR::Node *constrainI2l(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (node->getFirstChild()->isNonNegative())
        node->setIsNonNegative(true);

    return constrainWidenToLong(vp, node, TR::getMinSigned<TR::Int32>(), TR::getMaxSigned<TR::Int32>(), false);
}

TR::Node *constrainIu2l(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainWidenToLong(vp, node, TR::getMinUnsigned<TR::Int32>(), TR::getMaxUnsigned<TR::Int32>(), true);
}

void replaceWithSmallerType(OMR::ValuePropagation *vp, TR::Node *node)
{
#if 1
    return; // not enabled yet
#endif

    if (node->getReferenceCount() > 1)
        return;

    TR::DataType newType = node->getDataType();
    TR::Node *load = node->getFirstChild();

    TR::Compilation *comp = vp->comp();
    TR::SymbolReference *oldRef = load->getSymbolReference();

    if (!load->getOpCode().isLoadVar() || load->getReferenceCount() > 1 || !oldRef->getSymbol()->isAuto()
        || oldRef->getUseDefAliases().hasAliases())
        return;

    TR_UseDefInfo *useDefInfo = vp->_useDefInfo;

    if (!useDefInfo)
        return;

    uint16_t useIndex = load->getUseDefIndex();

    if (!useIndex || !useDefInfo->isUseIndex(useIndex))
        return;

    TR_UseDefInfo::BitVector defs(vp->comp()->allocator());
    if (!useDefInfo->getUseDef(defs, useIndex) || (defs.PopulationCount() > 1))
        return;
    TR_UseDefInfo::BitVector::Cursor cursor(defs);
    cursor.SetToFirstOne();
    int32_t defIndex = cursor;
    if (defIndex < useDefInfo->getFirstRealDefIndex())
        return;

    TR::Node *defNode = useDefInfo->getNode(defIndex);
    if (!defNode->getOpCode().isStore())
        return;

    TR_UseDefInfo::BitVector usesFromDefs(comp->allocator());
    useDefInfo->getUsesFromDef(usesFromDefs, defIndex);
    if (usesFromDefs.PopulationCount() > 1)
        return;

    TR::Node *value = defNode->getFirstChild();

    TR::ILOpCodes convOpcode
        = TR::ILOpCode::getProperConversion(value->getDataType(), newType, false /* !wantZeroExtension */);
    if (convOpcode == TR::BadILOp)
        return;

    if (performTransformation(comp,
            "%sReplace [" POINTER_PRINTF_FORMAT "] and [" POINTER_PRINTF_FORMAT
            "] with store and load of a smaller type\n",
            OPT_DETAILS, defNode, node)) {
        // Transform
        TR::Node *conv = TR::Node::create(convOpcode, 1, value);
        defNode->setAndIncChild(0, conv);
        value->recursivelyDecReferenceCount();

        // change opcode of the def and use nodes to store and load smaller type
        TR::Node::recreate(defNode, comp->il.opCodeForDirectStore(newType));
        TR::Node::recreate(node, comp->il.opCodeForDirectLoad(newType));
        node->getFirstChild()->recursivelyDecReferenceCount();
        node->setNumChildren(0);

        // create new temp
        TR::SymbolReference *symRef = comp->getSymRefTab()->createTemporary(vp->comp()->getMethodSymbol(), newType);
        defNode->setSymbolReference(symRef);
        node->setSymbolReference(symRef);
    }
}

static TR::Node *constrainNarrowIntValue(OMR::ValuePropagation *vp, TR::Node *node)
{
    if (findConstant(vp, node))
        return node;

    constrainChildren(vp, node);

    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node->getFirstChild(), isGlobal);
    if (constraint == NULL)
        return node;

    const int32_t resultSizeBits = 8 * TR::DataType::getSize(node->getDataType());
    const int64_t signBit = (int64_t)1 << (resultSizeBits - 1);
    const int32_t padding64 = 64 - resultSizeBits;
    const int64_t min = -signBit;
    const int64_t max = signBit - 1;

    // Find the range of values that the child could produce.
    int64_t low = 0;
    int64_t high = 0;
    if (constraint->asLongConstraint() != NULL || constraint->asMergedLongConstraints() != NULL) {
        low = constraint->getLowLong();
        high = constraint->getHighLong();
    } else if (constraint->asIntConstraint() != NULL || constraint->asMergedIntConstraints() != NULL) {
        low = constraint->getLowInt();
        high = constraint->getHighInt();
    } else if (constraint->asShortConstraint() != NULL || constraint->asMergedShortConstraints() != NULL) {
        low = constraint->getLowShort();
        high = constraint->getHighShort();
    } else {
        TR_ASSERT_FATAL_WITH_NODE(node, false, "bad integer narrowing child constraint");
    }

    TR_ASSERT_FATAL_WITH_NODE(node, low <= high, "reversed child constraint bounds");

    if (min <= low && high <= max) {
        // The child's value is the sign-extension of a resultSizeBits-bit value,
        // i.e. this conversion is lossless when the input and output are both
        // interpreted as signed.
        node->setCannotOverflow(true);
    }

    // Determine whether the range [low, high] contains two adjacent values that
    // truncate to max and min, e.g. for a byte result:
    //
    //    { low, ..., 0x..7f, 0x..80, ..., high }
    //
    // This occurs precisely when [low + signBit, high + signBit] contains two
    // adjacent values that truncate to the unsigned max and 0, e.g. for byte:
    //
    //    { low + signBit, ..., 0x..ff, 0x..00, ..., high + signBit }
    //
    // And, in turn, this is the case iff (low + signBit) and (high + signBit)
    // are still in the right order, i.e. low + signBit <= high + signBit, and
    // they differ in their upper bits, i.e. the bits that will be discarded.
    //
    // Cast to unsigned before adding to avoid undefined behaviour.
    //
    const int64_t biasedLow = (int64_t)((uint64_t)low + (uint64_t)signBit);
    const int64_t biasedHigh = (int64_t)((uint64_t)high + (uint64_t)signBit);
    if (biasedLow > biasedHigh) {
        // Signed overflow in (high + signBit) but not (low + signBit). Both min,
        // max are possible. To see why, let b=resultSizeBits. Then because of
        // the detected signed overflow, we know that
        //
        //    low + 2**(b-1) < 2**63     and     high + 2**(b-1) >= 2**63.
        //
        // This means that low < k <= high, where
        //
        //    k = 2**63 - 2**(b-1) = 2**(63-b) * 2**b - 2**(b-1).
        //
        // Since the truncation of k is min, and the truncation of k-1 is max, and
        // since k-1 and k are both in [low, high], both min and max are possible.
        //
        return node; // unconstrained
    }

    // There was overflow in both biasedLow and biasedHigh, or (more likely) in
    // neither, so checking the top bits will work.
    const int64_t lowTopBits = biasedLow >> resultSizeBits;
    const int64_t highTopBits = biasedHigh >> resultSizeBits;

    if (lowTopBits != highTopBits)
        return node; // unconstrained (min, max are both possible)

    // Since lowTopBits == highTopBits, truncation will always effectively
    // subtract the same multiple of 2**resultSizeBits from the signed value,
    // and so it preserves signed ordering, i.e. if x, y are possible values of
    // the child with x <= y, then truncate(x) <= truncate(y).
    //
    // To see why this must be the case, let b=resultSizeBits, let Q=lowTopBits,
    // and let x be the signed value of the child. Note that signBit = 2**(b-1).
    // Then
    //
    //    Q * 2**b <= low + 2**(b-1)
    //    Q * 2**b - 2**(b-1) <= low
    //
    // and
    //
    //    high + 2**(b-1) < (Q+1) * 2**b
    //    high < (Q+1) * 2**b - 2**(b-1) = Q * 2**b + 2**(b-1)
    //
    // Since low <= x <= high, we know that
    //
    //    Q * 2**b - 2**(b-1) <= low <= x <= high < Q * 2**b + 2**(b-1)
    //    -2**(b-1) <= x - Q * 2**b < 2**(b-1)
    //
    // Since [-2**(b-1), 2**(b-1)) is the signed range of the result type, the
    // signed value of the result is x - Q * 2**b for every x.
    //
    // So if x, y are possible signed child values with x <= y, then
    //
    //    truncate(x) = x - Q * 2**b <= y - Q * 2**b = truncate(y),
    //
    // and in particular since low, high are also possible signed child values
    // with low <= x <= high, we also know that
    //
    //    truncate(low) <= truncate(x) <= truncate(high).
    //
    // Therefore, truncating low and high will produce a signed range containing
    // all possible truncated results.
    //
    // NOTE: The above proof assumes that the addition of signBit in the biased
    // endpoints calculation does not signed-overflow, i.e. that the signed
    // value of the 64-bit sum is ({low,high} + 2**(b-1)). But it is possible
    // for such overflow to have occurred in both calculations. If it did, then
    // the signed values of the sums were really
    //
    //    {low,high} + 2**(b-1) - 2**64.
    //
    // In this case, we can simply take Q = topLowBits + 2**(64-b) and
    // everything else will still follow.
    //
    // With this justification, get the truncated (and re-extended) bounds. Cast
    // to unsigned before shifting left to avoid undefined behaviour.
    //
    const uint64_t origDiff = (uint64_t)high - (uint64_t)low;
    low = (int64_t)((uint64_t)low << padding64) >> padding64;
    high = (int64_t)((uint64_t)high << padding64) >> padding64;

    TR_ASSERT_FATAL_WITH_NODE(node, min <= low, "truncated lower bound is too low");
    TR_ASSERT_FATAL_WITH_NODE(node, low <= high, "truncated bounds are out of order");
    TR_ASSERT_FATAL_WITH_NODE(node, high <= max, "truncated upper bound is too high");
    TR_ASSERT_FATAL_WITH_NODE(node, (uint64_t)high - (uint64_t)low == origDiff,
        "truncated range is not the same size as the original range");

    if (low >= 0)
        node->setIsNonNegative(true);

    // Constrain the result.
    switch (node->getDataType()) {
        case TR::Int16:
            constraint = TR::VPShortRange::create(vp, (int16_t)low, (int16_t)high);
            break;

        case TR::Int8: // byte nodes are still using Int constraint
        case TR::Int32:
            constraint = TR::VPIntRange::create(vp, (int32_t)low, (int32_t)high);
            break;

        default:
            TR_ASSERT_FATAL_WITH_NODE(node, false, "Invalid node datatype");
    }

    if (constraint != NULL) // might have had low == min && high == max
    {
        if (low == high) {
            vp->replaceByConstant(node, constraint, isGlobal);
            return node;
        }

        vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
    }

    replaceWithSmallerType(vp, node);

    return node;
}

TR::Node *constrainNarrowToByte(OMR::ValuePropagation *vp, TR::Node *node) { return constrainNarrowIntValue(vp, node); }

TR::Node *constrainNarrowToShort(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainNarrowIntValue(vp, node);
}

TR::Node *constrainNarrowToInt(OMR::ValuePropagation *vp, TR::Node *node) { return constrainNarrowIntValue(vp, node); }

/*
static int32_t findConstantValue(OMR::ValuePropagation *vp, TR::Node *node, TR::VPConstraint *constraint)
   {
   int32_t value = constraint->asIntConst()->getInt();
   int32_t high = 0;
   switch (node->getOpCodeValue())
      {
      case TR::bu2i:
          high = TR::getMaxUnsigned<TR::Int8>()+1;
          break;
      case TR::su2i:
          high = TR::getMaxUnsigned<TR::Int16>()+1;
          break;
      }

   // if the consumer wants an unsigned value,
   // then find the right constant that needs to
   // be propagated. otherwise, the value is already
   // correct (i.e. if it is negative, then it needs to
   // be sign-extended as we widen)
   //
   if (high > 0)
      {
      // value is negative, which means that it wrapped
      // around on the smaller datatype's number line
      //
      if (value < 0)
         value = high + value;
      }
   return value;
   }
*/

static bool constrainWidenToInt(OMR::ValuePropagation *vp, TR::Node *&node, int32_t low, int32_t high, bool isUnsigned,
    TR::ILOpCodes correspondingNarrowingOpCode)
{
    if (findConstant(vp, node))
        return true;

    constrainChildren(vp, node);

    bool isGlobal;
    TR::Node *firstChild = node->getFirstChild();
    TR::Node *grandChild = firstChild->getNumChildren() > 0 ? firstChild->getFirstChild() : 0;
    TR::VPConstraint *constraint = vp->getConstraint(firstChild, isGlobal);
    TR::VPConstraint *preNarrowingConstraint = 0;
    bool yankConversionPair = false;

    // we can constant propagate here if the first child
    // is known to be a constant
    // FIXME: disabled as we'll let simplifier take care of this
    /*
    if (constraint && constraint->asIntConst())
       {
       ///int32_t result = findConstantValue(vp, node, constraint);
       ///TR::VPConstraint *c = TR::VPIntConst::create(vp, result);
       ///vp->replaceByConstant(node, c, isGlobal);
       vp->replaceByConstant(node, constraint, isGlobal);
       return true;
       }
    */

    if (firstChild->getOpCodeValue() == correspondingNarrowingOpCode) {
        preNarrowingConstraint = vp->getConstraint(firstChild->getFirstChild(), isGlobal);
        if (preNarrowingConstraint) {
            if (isUnsigned) {
                if (preNarrowingConstraint->getLowInt() >= 0 && preNarrowingConstraint->getHighInt() <= high) {
                    yankConversionPair = true;
                }
            } else {
                if (preNarrowingConstraint->getLowInt() >= low && preNarrowingConstraint->getHighInt() <= high) {
                    yankConversionPair = true;
                }
            }
        }
    }

    /// traceMsg(vp->comp(), "yankConversionPair %d node %p\n", yankConversionPair, node);
    TR::Node *origNode = node;
    // static const char *p = feGetEnv("TR_ConversionFolder");
    if ( // p &&
        yankConversionPair) {
        // have to increment the reference count of the grandchild to keep it from
        // being completele removed by removeNode
        // printf("found one (sign extension conversion removal) in method %s, node %p, low was %d, high was %d\n",
        // vp->comp()->signature(), grandChild, preNarrowingConstraint->getLowInt(),
        // preNarrowingConstraint->getHighInt());
        grandChild->incReferenceCount();
        vp->removeNode(node, false);
        node = grandChild;
    }

    if (constraint) {
        if (isUnsigned) {
            // Checks below to adjust high have been changed to >= (they were previously just >).
            //
            // The check for constraint->getLowInt() >= 0 is still needed. If the number was
            // defined as PIC S999 (3 digits, signed), the incoming range would be [-999, 999].
            // Widened to an unsigned integer, this gives the disjoint range [0, 999], [64537, 65535],
            // which, when merged into one constraint, would be [0, 65535]. In this case, low and high
            // should already be 0 and 65535, so we don't want to adjust anything based on the child
            // constraint.
            if (constraint->asShortConstraint()) {
                if (constraint->getLowShort() > 0)
                    low = constraint->getLowShort();
                if (constraint->getLowShort() >= 0 && constraint->getHighShort() < high)
                    high = constraint->getHighShort();
            } else {
                if (constraint->getLowInt() > 0)
                    low = constraint->getLowInt();
                if (constraint->getLowInt() >= 0 && constraint->getHighInt() < high)
                    high = constraint->getHighInt();
            }
        } else {
            // the underlying constraint is the unsigned bit representation of
            // the desired constraint
#if 0
         int32_t newLow, newHigh;
         if (constraint->asIntConst())
            {
            if (correspondingNarrowingOpCode == TR::i2s)
               low = (int16_t)constraint->getLowInt();
            else
               low = (int8_t)constraint->getLowInt();
            // constant
            high = low;
            }
         else // range
            {
            int32_t maxLow = low;
            int32_t maxHigh;
            if (correspondingNarrowingOpCode == TR::i2s)
               {
               newLow = (int16_t)constraint->getLowInt();
               newHigh = (int16_t)constraint->getHighInt();
               maxHigh = TR::getMaxUnsigned<TR::Int16>();
               }
            else
               {
               newLow = (int8_t)constraint->getLowInt();
               newHigh = (int8_t)constraint->getHighInt();
               maxHigh = TR::getMaxUnsigned<TR::Int8>();
               }
            // constrain the low
            if (newLow > low)
               low = newLow;
            // now constrain the high
            // check for overflow on the byte/short numberline
            // if so, then we don't know anything about the new range
            //
            if (constraint->getHighInt() > high) // overflow
               {
               low = maxLow;
               high = maxHigh;
               }
            else if (newHigh < high)
               high = newHigh;
            }
#else
            if (constraint->asShortConstraint()) {
                if (constraint->getLowShort() > low)
                    low = constraint->getLowShort();
                if (constraint->getHighShort() < high)
                    high = constraint->getHighShort();
            } else {
                if (constraint->getLowInt() > low)
                    low = constraint->getLowInt();
                if (constraint->getHighInt() < high)
                    high = constraint->getHighInt();
            }
#endif
        }
    }

    if (low <= high) {
        // Make sure the constraint is not trivial
        //
        constraint = TR::VPIntRange::create(vp, low, high);
        if (constraint) {
            // the node may have changed due to yankConversionPair
            // use the origNode to apply the constraint
            // consider the scenario
            // istore a
            //    c2i
            //      i2c
            //        iconst 50
            //  BNDCHK
            //     iconst 10
            //     iload a
            // 'a' and 'c2i' have the same valuenumber, but if the new
            // node is used (child of i2c) then the store constraint for 'a' is not
            // created (ie. when load 'a' is encountered later, there is no
            // matching constraint for the vn of 'a')
            // this is can be problematic for vp as the BNDCHK is never optimized
            // away and vp ends up analyzing dead code (the constraint for load 'a'
            // is now [0-10] which conflicts with [50])
            //
            vp->addBlockOrGlobalConstraint(origNode, constraint, isGlobal);
        }
        if (low >= 0)
            node->setIsNonNegative(true);

        if (high <= 0)
            node->setIsNonPositive(true);

        if ((node->getOpCode().isArithmetic() || node->getOpCode().isLoad())
            && ((low > TR::getMinSigned<TR::Int32>()) || (high < TR::getMaxSigned<TR::Int32>())))
            node->setCannotOverflow(true);
    }
    return false;
}

TR::Node *constrainB2i(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToInt(vp, node, static_cast<int32_t>(TR::getMinSigned<TR::Int8>()),
        static_cast<int32_t>(TR::getMaxSigned<TR::Int8>()), false, TR::i2b);
    return node;
}

TR::Node *constrainB2s(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToInt(vp, node, static_cast<int32_t>(TR::getMinSigned<TR::Int8>()),
        static_cast<int32_t>(TR::getMaxSigned<TR::Int8>()), false, TR::s2b);
    return node;
}

TR::Node *constrainBu2i(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToInt(vp, node, static_cast<int32_t>(TR::getMinUnsigned<TR::Int8>()),
        static_cast<int32_t>(TR::getMaxUnsigned<TR::Int8>()), true, TR::i2b);
    return node;
}

TR::Node *constrainBu2s(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToInt(vp, node, static_cast<int32_t>(TR::getMinUnsigned<TR::Int8>()),
        static_cast<int32_t>(TR::getMaxUnsigned<TR::Int8>()), true, TR::s2b);
    return node;
}

TR::Node *constrainB2l(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToLong(vp, node, TR::getMinSigned<TR::Int8>(), TR::getMaxSigned<TR::Int8>(), false);
    return node;
}

TR::Node *constrainBu2l(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToLong(vp, node, TR::getMinUnsigned<TR::Int8>(), TR::getMaxUnsigned<TR::Int8>(), true);
    return node;
}

TR::Node *constrainS2i(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToInt(vp, node, static_cast<int32_t>(TR::getMinSigned<TR::Int16>()),
        static_cast<int32_t>(TR::getMaxSigned<TR::Int16>()), false, TR::i2s);
    return node;
}

TR::Node *constrainSu2i(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToInt(vp, node, static_cast<int32_t>(TR::getMinUnsigned<TR::Int16>()),
        static_cast<int32_t>(TR::getMaxUnsigned<TR::Int16>()), true, TR::i2s);
    return node;
}

TR::Node *constrainS2l(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToLong(vp, node, TR::getMinSigned<TR::Int16>(), TR::getMaxSigned<TR::Int16>(), false);
    return node;
}

TR::Node *constrainSu2l(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainWidenToLong(vp, node, TR::getMinUnsigned<TR::Int16>(), TR::getMaxUnsigned<TR::Int16>(), true);
    return node;
}

static void generateModifiedNopGuard(OMR::ValuePropagation *vp, TR::Node *guardNode, TR_VirtualGuardKind kind)
{
    TR::Compilation *comp = vp->comp();

    TR_ASSERT_FATAL_WITH_NODE(guardNode, guardNode->isProfiledGuard() || kind == TR_HCRGuard,
        "can only create virtual guards based on profiled guards");

    TR_ASSERT_FATAL_WITH_NODE(guardNode, guardNode->getOpCodeValue() == TR::ifacmpne, "expected ifacmpne");

    int32_t calleeIndex = guardNode->getInlinedSiteIndex();
    TR_InlinedCallSite &site = comp->getInlinedCallSite(calleeIndex);

    TR_ByteCodeInfo callBci = site._byteCodeInfo;
    TR_OpaqueMethodBlock *inlinedMethod = site._methodInfo;
    TR_ResolvedMethod *inlinedResolvedMethod = comp->fe()->createResolvedMethod(comp->trMemory(), inlinedMethod);

    TR_ASSERT_FATAL_WITH_NODE(guardNode, !comp->compileRelocatableCode(),
        "can't necessarily cook up a guard of kind %d in a relocatable compilation", (int)kind);

    // Set up the correct inlined call stack. This is needed because guard
    // creation looks at at the comp object's 'current inlined site index.'
    //
    // For now, assume that we start (and will therefore end) with no inlined
    // frames that are active/current according to the comp object. This would
    // be violated if VP were run during inlining as part of ilgen opts. If
    // needed in the future, it should be possible to allow that scenario by:
    // - relaxing this assertion, and
    // - when done, conditionally using restoreInlineDepth(TR_ByteCodeInfo&)
    //   instead of resetInlineDepth().
    //
    TR_ASSERT_FATAL_WITH_NODE(guardNode, comp->getInlineDepth() == 0,
        "trying to upgrade to a nop guard: VP is running during inlining");

    comp->adjustInlineDepth(callBci);

    TR::Node *newGuardNode = NULL;

    // Create a fake call node with a symref that identifies the actual method
    // that was inlined, which can be more specific (i.e. belong to a
    // more-derived type) than the one used for the original call. The guard
    // won't remember the call node anyway (since only guards for codegen's
    // guarded devirtualization do that). The call node is only used to get the
    // symref and BCI.
    //
    // This is important for nonoverridden guards because the symref specifies
    // the method that must not be overridden.
    //
    // This is important for hierarchy guards to ensure that we get a resolved
    // method symbol. Otherwise it could be confused for an interface guard.
    //
    // HCR guards need the ResolvedMethodSymbol from the symref.
    //
    // Using a fake call node this way also means that it's not necessary to
    // find the original call node in the trees.
    //
    TR::SymbolReference *inlinedMethodSR = comp->getSymRefTab()->findOrCreateMethodSymbol(JITTED_METHOD_INDEX,
        -1, // CP index
        inlinedResolvedMethod, TR::MethodSymbol::Virtual);

    TR::ResolvedMethodSymbol *inlinedMethodSym = inlinedMethodSR->getSymbol()->castToResolvedMethodSymbol();

    TR::ILOpCodes fakeCallOp = inlinedResolvedMethod->indirectCallOpCode();
    TR::Node *fakeCallNode = TR::Node::createWithSymRef(fakeCallOp, 0, inlinedMethodSR);

    fakeCallNode->setByteCodeInfo(callBci);

    // Remember the guard's receiver class, and also whether the guard is merged
    // with an HCR or an OSR guard.
    TR_VirtualGuard *guard = comp->findVirtualGuardInfo(guardNode);
    TR_OpaqueClassBlock *thisClass = guard->getThisClass();
    bool mergedWithHCRGuard = guard->mergedWithHCRGuard();
    bool mergedWithOSRGuard = guard->mergedWithOSRGuard();

    // Remove the original guard, since node will be removed from the trees.
    comp->removeVirtualGuard(guard);

    // Actually create the new guard.
    switch (kind) {
        // When the caller requests nonoverridden guard, the newly created guard
        // will be patched if/when the inlined method is overridden.
        case TR_NonoverriddenGuard: {
            newGuardNode = TR_VirtualGuard::createNonoverriddenGuard(TR_NonoverriddenGuard, comp, calleeIndex,
                fakeCallNode, guardNode->getBranchDestination(), inlinedMethodSym, true /* forInline */);

            break;
        }

        // When the caller requests hierarchy guard, the existing guardNode
        // must use a VFT test, and the newly created guard will be patched
        // if/when the class identified in the VFT test is extended.
        case TR_HierarchyGuard: {
            TR::Node *expectedClassNode = guardNode->getChild(1);
            TR_ASSERT_FATAL_WITH_NODE(guardNode, expectedClassNode->getOpCodeValue() == TR::aconst,
                "VFT test expected class child should be aconst");

            TR::Node *vftLoadNode = guardNode->getChild(0);
            TR::SymbolReference *vftSR = comp->getSymRefTab()->findVftSymbolRef();

            TR_ASSERT_FATAL_WITH_NODE(guardNode,
                vftLoadNode->getOpCodeValue() == TR::aloadi && vftLoadNode->getSymbolReference() == vftSR,
                "VFT test expected receiver VFT child should be a VFT load");

            TR::Node *receiverNode = vftLoadNode->getChild(0);

            auto expectedClassAddr = static_cast<uintptr_t>(expectedClassNode->getAddress());

            auto *expectedClass = reinterpret_cast<TR_OpaqueClassBlock *>(expectedClassAddr);

            TR_ASSERT_FATAL_WITH_NODE(guardNode, expectedClass == thisClass,
                "VFT test class %p differs from thisClass %p", expectedClass, thisClass);

            newGuardNode = TR_VirtualGuard::createVftGuardWithReceiver(TR_HierarchyGuard, comp, calleeIndex,
                fakeCallNode, guardNode->getBranchDestination(), thisClass, receiverNode);

            break;
        }

        // When the caller requests HCR guard, the newly created guard will be
        // patched when the receiver class of the original guard is redefined.
        // That class must be the defining class of the inlined method, since an
        // HCR guard will only be requested when the original guard was
        // mergedWithHCRGuard().
        case TR_HCRGuard: {
            TR_ASSERT_FATAL_WITH_NODE(guardNode, mergedWithHCRGuard, "unexpected HCR guard request");

            TR_ASSERT_FATAL_WITH_NODE(guardNode, thisClass == inlinedResolvedMethod->containingClass(),
                "HCR assumption class mismatch");

            mergedWithHCRGuard = false; // new guard will be a standalone HCR guard
            mergedWithOSRGuard = false; // new guard represents only the HCR aspect

            newGuardNode = TR_VirtualGuard::createHCRGuard(comp, calleeIndex, fakeCallNode,
                guardNode->getBranchDestination(), inlinedMethodSym, thisClass);

            break;
        }

        default:
            TR_ASSERT_FATAL(false, "unexpected guard kind %d", (int)kind);
    }

    // Preserve guard merging, if any.
    guard = comp->findVirtualGuardInfo(newGuardNode);
    guard->setThisClass(thisClass);

    if (mergedWithHCRGuard)
        guard->setMergedWithHCRGuard();

    if (mergedWithOSRGuard)
        guard->setMergedWithOSRGuard();

    TR::TreeTop *newGuardTT = TR::TreeTop::create(comp, newGuardNode);
    vp->_curTree->insertAfter(newGuardTT);

    TR_Debug *dbg = comp->getDebug();
    dumpOptDetails(comp, "Generated %s n%un [%p]\n", dbg == NULL ? "guard" : dbg->getVirtualGuardKindName(kind),
        newGuardNode->getGlobalIndex(), newGuardNode);

    comp->resetInlineDepth();
}

static void changeConditionalToGoto(OMR::ValuePropagation *vp, TR::Node *node, TR::CFGEdge *branchEdge)
{
    // NOTE: No special handling is required here to deal with the possibility
    // that node is a virtual guard that has been merged with an HCR or an OSR
    // guard. It's always safe to go to the taken (cold) side.

    // Fall through path is unreachable.
    // Set the current constraints to "unreachable path"
    //
    vp->setUnreachablePath();

    // Remove the children and change the node to a goto,
    //
    node->setVirtualGuardInfo(NULL, vp->comp());
    vp->removeChildren(node, false);
    TR::Node::recreate(node, TR::Goto);
    vp->setEnableSimplifier();

    // Remember that the fall-through edge is to be removed
    // But do not remove it if fall-through and target blocks
    // are the same.
    TR::Block *fallThrough = vp->_curBlock->getExit()->getNextTreeTop()->getNode()->getBlock();
    TR::CFGEdge *edge = vp->findOutEdge(vp->_curBlock->getSuccessors(), fallThrough);
    TR::Block *target = node->getBranchDestination()->getNode()->getBlock();
    if (fallThrough != target)
        vp->_edgesToBeRemoved->add(edge);
    vp->printEdgeConstraints(vp->createEdgeConstraints(edge, true));
}

static void removeConditionalBranch(OMR::ValuePropagation *vp, TR::Node *node, TR::CFGEdge *branchEdge)
{
    // If node is a virtual guard merged with an HCR or an OSR guard, then it
    // will still be possible to go to the cold side. In that case, node will
    // still be removed, but an HCR or OSR guard will be added in its place, so
    // the taken edge must still be considered reachable, and it must not be
    // added to _edgesToBeRemoved.
    bool edgeIsUnreachable = true;

    if (node->isTheVirtualGuardForAGuardedInlinedCall()) {
        TR_VirtualGuard *guard = vp->comp()->findVirtualGuardInfo(node);

        // I don't think we have any way to generate a virtual guard that is
        // merged with both HCR and OSR guards. The HCR guard would be redundant.
        TR_ASSERT_FATAL_WITH_NODE(node, !guard->mergedWithHCRGuard() || !guard->mergedWithOSRGuard(),
            "virtual guard is merged with both an HCR and an OSR guard");

        if (guard->mergedWithHCRGuard()) {
            dumpOptDetails(vp->comp(), "Separating HCR guard from n%un [%p]\n", node->getGlobalIndex(), node);

            generateModifiedNopGuard(vp, node, TR_HCRGuard);

            // The branch edge is still reachable via the HCR guard.
            edgeIsUnreachable = false;
        } else if (guard->mergedWithOSRGuard()) {
            TR::Node *osrGuardNode = TR_VirtualGuard::createOSRGuard(vp->comp(), node->getBranchDestination());

            TR::TreeTop *osrGuardTT = TR::TreeTop::create(vp->comp(), osrGuardNode);
            vp->_curTree->insertAfter(osrGuardTT);

            dumpOptDetails(vp->comp(), "Separated OSR guard from n%un [%p]. New OSR guard is n%un [%p].\n",
                node->getGlobalIndex(), node, osrGuardNode->getGlobalIndex(), osrGuardNode);

            // The branch edge is still reachable via the OSR guard.
            edgeIsUnreachable = false;
        }

        guard->setMergedWithHCRGuard(false);
        guard->setMergedWithOSRGuard(false);
    }

    // Branch path is unreachable.
    // Set the edge constraints on the branch path to "unreachable path"
    //
    if (edgeIsUnreachable)
        vp->setUnreachablePath(branchEdge);

    // Remove this tree.
    //
    vp->removeNode(node, false);
    vp->_curTree->setNode(NULL);
    vp->setEnableSimplifier();
    // Do not remove the branch edge if both
    // fall-through and target blocks are the same.
    TR::Block *fallThrough = vp->_curBlock->getExit()->getNextTreeTop()->getNode()->getBlock();
    TR::Block *target = node->getBranchDestination()->getNode()->getBlock();
    if (fallThrough != target && edgeIsUnreachable)
        vp->_edgesToBeRemoved->add(branchEdge);
}

// Constrain objects that pass a type test, given a constraint on the class
// against which the object is to be tested.
static TR::VPConstraint *passingTypeTestObjectConstraint(OMR::ValuePropagation *vp, TR::VPConstraint *classConstraint,
    bool testingForFixedType, bool constrainVft)
{
    TR_ASSERT_FATAL(classConstraint->isClassObject() == TR_yes,
        "expected a instanceof classConstraint to be a 'ClassObject'");

    TR::VPClassType *type = classConstraint->getClassType();
    TR_ASSERT_FATAL(type != NULL, "expected instanceof classConstraint to have a type");

    // Even if we know the exact type tested against, objects passing the type
    // test may be instances of derived classes, so relax to a bound.
    if (!testingForFixedType && type->isFixedClass()) {
        // Note: if type->getClass() is final, this will still result in a
        // fixed-class constraint, but that's fine.
        type = TR::VPResolvedClass::create(vp, type->getClass());
    }

    TR::VPObjectLocation *loc = NULL;
    if (constrainVft) {
        // The resulting constraint applies to the object's VFT pointer, not to
        // the object itself.
        loc = TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject);
    } else {
        // Use the signature to check whether type is java/lang/Class exactly. We
        // can't rely on isJavaLangClassObject() because it returns TR_maybe in this
        // case. (If it were to return TR_yes, it would mean the referent is a
        // java/lang/Class representing java/lang/Class, i.e. Class.class, which
        // would be incorrect in this case.)
        int sigLen;
        const char *sig = type->getClassSignature(sigLen);
        if (sig != NULL && sigLen == 17 && !strncmp(sig, "Ljava/lang/Class;", sigLen)) {
            // Just say that it's a java/lang/Class, with no information about the
            // class that it represents.
            type = NULL;
            loc = TR::VPObjectLocation::create(vp, TR::VPObjectLocation::JavaLangClassObject);
        }
    }

    // Objects passing any kind of instanceof are non-null:
    // - instanceof itself is only true for non-null objects.
    // - In a more direct type test that unconditionally loads the VFT
    //   pointer, the object must be non-null even if it's a different type.
    TR::VPNonNullObject *nonnull = TR::VPNonNullObject::create(vp);
    TR::VPConstraint *newConstraint = TR::VPClass::create(vp, type, nonnull, NULL, NULL, loc);

    TR_ASSERT_FATAL(newConstraint != NULL, "failed to create constraint");

    if (vp->trace()) {
        traceMsg(vp->comp(), "passingTypeTestObjectConstraint returning constraint: ");
        newConstraint->print(vp->comp(), vp->comp()->getOutFile());
        traceMsg(vp->comp(), "\n");
    }

    return newConstraint;
}

// Return true if the guard was successfully upgraded.
static bool upgradeToNopGuard(OMR::ValuePropagation *vp, TR::Node *guardNode, TR_VirtualGuardKind kind)
{
    if (!vp->lastTimeThrough() || vp->comp()->compileRelocatableCode())
        return false;

    static const bool disable = feGetEnv("TR_disableUpgradeToNopGuard") != NULL;

    if (disable)
        return false;

    if (TR::Compiler->vm.isVMInStartupPhase(vp->comp())) {
        static const bool enableDuringStartup = feGetEnv("TR_upgradeToNopGuardDuringStartup") != NULL;

        if (!enableDuringStartup)
            return false;
    }

    TR_Debug *dbg = vp->comp()->getDebug();
    if (!performTransformation(vp->comp(), "%sUpgrading profiled guard n%un [%p] to %s\n", OPT_DETAILS,
            guardNode->getGlobalIndex(), guardNode, dbg == NULL ? "<nop guard>" : dbg->getVirtualGuardKindName(kind))) {
        return false; // permission denied
    }

    // This inserts a new tree with the new guard immediately after the existing
    // one, so VP will go on to constrain it as soon as it's done with the
    // current tree.
    generateModifiedNopGuard(vp, guardNode, kind);

    vp->removeNode(guardNode, false);
    vp->_curTree->setNode(NULL);
    vp->setEnableSimplifier();

    return true;
}

// Handles ificmpeq, ificmpne, iflcmpeq, iflcmpne, ifacmpeq, ifacmpne
//
static TR::Node *constrainIfcmpeqne(OMR::ValuePropagation *vp, TR::Node *node, bool branchOnEqual)
{
    constrainChildren(vp, node);

    // Find the output edge from the current block that corresponds to this
    // branch
    //
    TR::Block *target = node->getBranchDestination()->getNode()->getBlock();
    TR::Block *fallThrough = vp->_curBlock->getNextBlock();
    // check for target and fall through edges being the same
    if (fallThrough == target)
        return node;

    TR::Node *lhsChild = node->getFirstChild();
    TR::Node *rhsChild = node->getSecondChild();

    bool isGlobal;
    TR::VPConstraint *lhs = NULL;
    TR::VPConstraint *rhs = NULL;
    TR::VPConstraint *rel = NULL;

    if (rhsChild->getOpCodeValue() == TR::iconst && lhsChild->getOpCode().isCompareForEquality()) {
        int32_t rhsConst = rhsChild->getInt();
        if (rhsConst == 0 || rhsConst == 1) {
            TR::Node *lhsGrandchild = lhsChild->getChild(0);
            TR::Node *rhsGrandchild = lhsChild->getChild(1);
            TR::ILOpCode lhsOp = lhsGrandchild->getOpCode();
            if (lhsOp.isInt() || lhsOp.isLong() || lhsOp.isRef()) {
                TR::ILOpCode lhsCmp = lhsChild->getOpCode();
                TR::ILOpCode newIfOp = lhsCmp.convertCmpToIfCmp();
                if (branchOnEqual != (rhsConst != 0))
                    newIfOp = newIfOp.getOpCodeForReverseBranch();

                if (performTransformation(vp->comp(), "%sChanging n%un (%s (%s ...) %d) to (%s ...)\n", OPT_DETAILS,
                        node->getGlobalIndex(), node->getOpCode().getName(), lhsCmp.getName(), rhsConst,
                        newIfOp.getName())) {
                    TR::Node::recreate(node, newIfOp.getOpCodeValue());
                    node->setAndIncChild(0, lhsGrandchild);
                    node->setAndIncChild(1, rhsGrandchild);
                    lhsChild->recursivelyDecReferenceCount();
                    rhsChild->recursivelyDecReferenceCount();
                    lhsChild = lhsGrandchild;
                    rhsChild = rhsGrandchild;
                    branchOnEqual = newIfOp.isCompareTrueIfEqual();
                }
            }
        }
    }

    TR::CFGEdge *edge = vp->findOutEdge(vp->_curBlock->getSuccessors(), target);

    bool cannotBranch = false;
    bool cannotFallThrough = false;

    // quick check for virtual guards to
    // facilitate (in)equality checks below
    // vguards are handled specially later
    //
    bool ignoreVirtualGuard = true;
    TR_VirtualGuard *virtualGuard = node->virtualGuardInfo();
    if (virtualGuard != NULL && !virtualGuard->canBeRemoved()) {
        // Merged guards are handled properly in removeConditionalBranch()
        // (except in AOT compilations).
        ignoreVirtualGuard = virtualGuard->canBeRemoved(true) && !vp->comp()->compileRelocatableCode();

        if (vp->trace()) {
            const char *msg
                = ignoreVirtualGuard ? "is ok to remove (if possible) despite merged guards" : "cannot be removed";

            traceMsg(vp->comp(), "Virtual guard n%un [%p] %s.\n", node->getGlobalIndex(), node, msg);
        }
    }

    // Quick check for the same value number
    //
    if (vp->getValueNumber(lhsChild) == vp->getValueNumber(rhsChild) && ignoreVirtualGuard) {
        if (vp->trace())
            traceMsg(vp->comp(), "   cmp children have same value number: %p = %p = %d\n", lhsChild, rhsChild,
                vp->getValueNumber(lhsChild));
        if (branchOnEqual)
            cannotFallThrough = true;
        else
            cannotBranch = true;
    }

    // See if there are absolute constraints on the children
    //
    else {
        lhs = vp->getConstraint(lhsChild, isGlobal);
        rhs = vp->getConstraint(rhsChild, isGlobal);

        if (lhs && rhs) {
            if (vp->trace()) {
                traceMsg(vp->comp(), "   cmp %p child absolute constraints:\n      %p: ", node, lhsChild);
                lhs->print(vp);
                traceMsg(vp->comp(), "\n      %p: ", rhsChild);
                rhs->print(vp);
                traceMsg(vp->comp(), "\n");
            }
            if (lhs->mustBeEqual(rhs, vp) && ignoreVirtualGuard) {
                if (vp->trace())
                    traceMsg(vp->comp(), "   cmp children must be equal by absolute constraints: %p == %p\n", lhsChild,
                        rhsChild);
                if (branchOnEqual)
                    cannotFallThrough = true;
                else
                    cannotBranch = true;
            } else if (lhs->mustBeNotEqual(rhs, vp) && ignoreVirtualGuard) {
                if (vp->trace())
                    traceMsg(vp->comp(), "   cmp children must be not equal by absolute constraints: %p != %p\n",
                        lhsChild, rhsChild);
                if (branchOnEqual)
                    cannotBranch = true;
                else
                    cannotFallThrough = true;
            }
        }
    }

    // See if there are relative constraints on the children
    //
    if (!cannotBranch && !cannotFallThrough) {
        rel = vp->getConstraint(lhsChild, isGlobal, rhsChild);
        if (rel && ignoreVirtualGuard) {
            if (vp->trace()) {
                traceMsg(vp->comp(), "   cmp %p child relative constraint: ", node);
                rel->print(vp);
                traceMsg(vp->comp(), "\n");
            }
            if (rel->mustBeEqual()) {
                if (vp->trace())
                    traceMsg(vp->comp(), "   cmp children must be equal by relative constraint: %p == %p\n", lhsChild,
                        rhsChild);
                if (branchOnEqual)
                    cannotFallThrough = true;
                else
                    cannotBranch = true;
            } else if (rel->mustBeNotEqual()) {
                if (vp->trace())
                    traceMsg(vp->comp(), "   cmp children must be not equal by relative constraint: %p != %p\n",
                        lhsChild, rhsChild);
                if (branchOnEqual)
                    cannotBranch = true;
                else
                    cannotFallThrough = true;
            }
        }
    }

    if (cannotBranch
        && performTransformation(vp->comp(), "%sRemoving conditional branch [%p] %s\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        removeConditionalBranch(vp, node, edge);
        return node;
    }

    if (cannotFallThrough
        && performTransformation(vp->comp(), "%sChanging node [%p] %s into goto\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        vp->printEdgeConstraints(vp->createEdgeConstraints(edge, false));
        changeConditionalToGoto(vp, node, edge);
        return node;
    }

#ifdef J9_PROJECT_SPECIFIC

    if (!vp->comp()->compileRelocatableCode() && vp->lastTimeThrough() && vp->comp()->performVirtualGuardNOPing()
        && !vp->_curBlock->isCold() && !vp->_curBlock->getNextBlock()->isExtensionOfPreviousBlock()
        && (node->getNumChildren() == 2)
        && ((node->getOpCodeValue() == TR::ifacmpeq) || (node->getOpCodeValue() == TR::ifacmpne))) {
        TR::Node *first = node->getFirstChild();
        TR::Node *second = node->getSecondChild();
        if ((second->getOpCodeValue() == TR::aconst) && (second->getAddress() == 0)) {
            const char *clazzToBeInitialized = NULL;
            int32_t clazzNameLen = -1;
            bool isGlobal;
            TR::VPConstraint *objectRefConstraint = vp->getConstraint(first, isGlobal);
            if (objectRefConstraint) {
                TR::VPClassType *typeConstraint = objectRefConstraint->getClassType();
                if (typeConstraint) {
                    TR::VPConstraint *resolvedTypeConstraint = typeConstraint->asResolvedClass();
                    bool allowForAOT = vp->comp()->getOption(TR_UseSymbolValidationManager);
                    if (resolvedTypeConstraint) {
                        TR_OpaqueClassBlock *clazz = resolvedTypeConstraint->getClass();

                        TR_PersistentClassInfo *classInfo
                            = vp->comp()->getPersistentInfo()->getPersistentCHTable()->findClassInfoAfterLocking(clazz,
                                vp->comp(), allowForAOT);

                        if (vp->trace())
                            traceMsg(vp->comp(), "MyDebug: clazz %p classInfo %p classInfo->isInitialized() %d\n",
                                clazz, classInfo, classInfo ? classInfo->isInitialized() : -1);
                        if (!classInfo || !classInfo->isInitialized()) {
                            int32_t len;
                            const char *sig = resolvedTypeConstraint->getClassSignature(len);
                            clazzToBeInitialized = sig;
                            clazzNameLen = len;
                        }
                    } else {
                        TR::VPUnresolvedClass *unresolvedTypeConstraint = typeConstraint->asUnresolvedClass();
                        if (unresolvedTypeConstraint) {
                            int32_t len;
                            const char *sig = unresolvedTypeConstraint->getClassSignature(len);
                            TR_ResolvedMethod *owningMethod = unresolvedTypeConstraint->getOwningMethod();
                            if (owningMethod) {
                                TR_OpaqueClassBlock *clazz = vp->fe()->getClassFromSignature(sig, len, owningMethod);
                                if (!clazz) {
                                    clazzToBeInitialized = sig;
                                    clazzNameLen = len;
                                } else {
                                    TR_PersistentClassInfo *classInfo
                                        = vp->comp()
                                              ->getPersistentInfo()
                                              ->getPersistentCHTable()
                                              ->findClassInfoAfterLocking(clazz, vp->comp(), allowForAOT);

                                    if (vp->trace())
                                        traceMsg(vp->comp(),
                                            "MyDebug: clazz %p classInfo %p classInfo->isInitialized() %d\n", clazz,
                                            classInfo, classInfo ? classInfo->isInitialized() : -1);
                                    if (!classInfo || !classInfo->isInitialized()) {
                                        clazzToBeInitialized = sig;
                                        clazzNameLen = len;
                                    }
                                }
                            }
                        }
                    }

                    if (!vp->comp()->getGenerateReadOnlyCode() && clazzToBeInitialized
                        && (((clazzNameLen == 35)
                                && !strncmp(clazzToBeInitialized, "Lcom/ibm/ejs/ras/TraceEnabledToken;", clazzNameLen))
                            || ((clazzNameLen == 41)
                                && !strncmp(clazzToBeInitialized, "Lcom/ibm/websphere/ras/TraceEnabledToken;",
                                    clazzNameLen))
                            || ((clazzNameLen == 40)
                                && !strncmp(clazzToBeInitialized, "Ljava/lang/String$StringCompressionFlag;",
                                    clazzNameLen)))
                        && performTransformation(vp->comp(),
                            "%sUsing side-effect guard to fold away condition on a load from an uninitializec class  "
                            "%s [%p]\n",
                            OPT_DETAILS, clazzToBeInitialized, node)) {
                        char *clazzToBeInitializedCopy
                            = (char *)vp->trMemory()->allocateMemory(clazzNameLen + 1, heapAlloc);
                        strcpy(clazzToBeInitializedCopy, clazzToBeInitialized);
                        vp->_classesToCheckInit.add(new (vp->trStackMemory())
                                OMR::ValuePropagation::ClassInitInfo(vp, clazzToBeInitializedCopy, clazzNameLen));
                    }
                }
            }
        }
    }
#endif

    // Propagate current constraints to the branch target
    //
    if (vp->trace())
        traceMsg(vp->comp(), "   Conditional branch\n");
    OMR::ValuePropagation::EdgeConstraints *edgeConstraints = vp->createEdgeConstraints(edge, true);

    // Find constraints to apply to the not-equal edge
    //
    TR::VPConstraint *notEqualConstraint = NULL;
    TR::Node *constraintChild = NULL;
    if (lhs) {
        constraintChild = rhsChild;
        if (lhs->isNullObject())
            notEqualConstraint = TR::VPNonNullObject::create(vp);
        else if (lhs->asIntConst()) {
            // if (lhs->isUnsigned())
            //    notEqualConstraint = TR::VPIntConst::createExclusion(vp, lhs->asIntConst()->getInt(), true);
            // else
            notEqualConstraint = TR::VPIntConst::createExclusion(vp, lhs->asIntConst()->getInt());
        } else if (lhs->asLongConst())
            notEqualConstraint = TR::VPLongConst::createExclusion(vp, lhs->asLongConst()->getLong());
    }
    if (rhs && !notEqualConstraint) {
        constraintChild = lhsChild;
        if (rhs->isNullObject())
            notEqualConstraint = TR::VPNonNullObject::create(vp);
        else if (rhs->asIntConst()) {
            // if (rhs->isUnsigned())
            //    notEqualConstraint = TR::VPIntConst::createExclusion(vp, rhs->asIntConst()->getInt(), true);
            // else
            notEqualConstraint = TR::VPIntConst::createExclusion(vp, rhs->asIntConst()->getInt());
        } else if (rhs->asLongConst())
            notEqualConstraint = TR::VPLongConst::createExclusion(vp, rhs->asLongConst()->getLong());
    }

    // See if one child is an instanceof and the other is a constant. If so, we
    // can apply a type constraint to the object child of the instanceof along
    // the path where the instanceof is true.
    //
    TR::VPConstraint *instanceofConstraint = NULL;
    TR::Node *instanceofObjectRef = NULL;
    bool instanceofObjectRefIsVft = false;
    bool instanceofOnBranch = false;
    bool instanceofDetectedAndFixedType = false;
    bool isInstanceOf = false;

    if (lhsChild->getOpCodeValue() == TR:: instanceof && rhs && rhs->asIntConst()) {
        int32_t value = rhs->asIntConst()->getInt();
        if (value == 0 || value == 1) {
            instanceofObjectRef = lhsChild->getFirstChild();
            TR::Node *classChild = lhsChild->getSecondChild();
            TR::VPConstraint *classConstraint = vp->getConstraint(classChild, isGlobal);
            if (classConstraint && classConstraint->getClassType()) {
                instanceofConstraint = classConstraint;
                isInstanceOf = true;
                if (value ^ (int32_t)branchOnEqual)
                    instanceofOnBranch = false;
                else
                    instanceofOnBranch = true;
            }
        }
    }

    // don't propagate the classConstraint to the object if the guard is nopable
    //
    bool isVirtualGuardNopable = node->isNopableInlineGuard();

    // Only accept VFT and method tests using ifacmpne here. Reversed (ifacmpeq)
    // guards are very rare, and shouldn't necessarily even be allowed, but they
    // are currently possible.
    if (node->getOpCodeValue() == TR::ifacmpne && virtualGuard != NULL) {
        TR::VPConstraint *guardClassConstraint = NULL;
        switch (virtualGuard->getTestType()) {
            case TR_VftTest: {
#ifdef J9_PROJECT_SPECIFIC
                instanceofObjectRef = node->getFirstChild();
                TR::Node *classChild = node->getSecondChild();
                guardClassConstraint = vp->getConstraint(classChild, isGlobal);
                static const char *enableJavaLangClassFolding = feGetEnv("TR_EnableFoldJavaLangClass");
                if (enableJavaLangClassFolding && (instanceofObjectRef->getOpCodeValue() == TR::aloadi)
                    && (instanceofObjectRef->getSymbolReference() == vp->comp()->getSymRefTab()->findVftSymbolRef())) {
                    TR::Node *objectChild = instanceofObjectRef->getFirstChild();
                    TR::VPConstraint *objectConstraint = vp->getConstraint(objectChild, isGlobal);

                    // if objectConstraint is equal to java/lang/Class we can fold the guard
                    if (ignoreVirtualGuard && guardClassConstraint && guardClassConstraint->getClassType()
                        && objectConstraint && objectConstraint->isClassObject() == TR_yes
                        && vp->comp()->getClassClassPointer()) {
                        bool childrenAreEqual = vp->comp()->getClassClassPointer() == guardClassConstraint->getClass();
                        if (childrenAreEqual)
                            cannotBranch = true;
                        else
                            cannotFallThrough = true;
                    }
                }

                TR::SymbolReference *vftSR = vp->comp()->getSymRefTab()->findVftSymbolRef();

                if (ignoreVirtualGuard && !cannotFallThrough && !cannotBranch
                    && classChild->getOpCodeValue() == TR::aconst && instanceofObjectRef->getOpCodeValue() == TR::aloadi
                    && instanceofObjectRef->getSymbolReference() == vftSR && node->isProfiledGuard()) {
                    TR::VPConstraint *receiverClassConstraint = vp->getConstraint(instanceofObjectRef, isGlobal);

                    if (receiverClassConstraint != NULL && receiverClassConstraint->isClassObject() == TR_yes
                        && receiverClassConstraint->getClass() != NULL) {
                        auto expectedClassAddr = static_cast<uintptr_t>(classChild->getAddress());

                        auto expectedClass = reinterpret_cast<TR_OpaqueClassBlock *>(expectedClassAddr);

                        TR_OpaqueClassBlock *clazz = receiverClassConstraint->getClass();

                        // There's no point checking for subtyping here, since
                        // a class that hasn't been extended has no subtypes.
                        if (clazz == expectedClass && !vp->comp()->fe()->classHasBeenExtended(expectedClass)
                            && upgradeToNopGuard(vp, node, TR_HierarchyGuard)) {
                            return node;
                        }
                    }
                }
#endif
            } break;

            case TR_MethodTest: {
                TR::Node *vtableEntryNode = node->getFirstChild();
                if (vtableEntryNode->getOpCodeValue() != TR::aloadi)
                    break; // unexpected tree shape

                TR::SymbolReference *entrySR = vtableEntryNode->getSymbolReference();
                if (!vp->comp()->getSymRefTab()->isVtableEntrySymbolRef(entrySR))
                    break; // unexpected tree shape

                TR::Node *methodPtrNode = node->getSecondChild();
                if (methodPtrNode->getOpCodeValue() != TR::aconst)
                    break; // unexpected tree shape

                auto expectedMethodAddr = static_cast<intptr_t>(methodPtrNode->getAddress());

                TR_ASSERT_FATAL_WITH_NODE(methodPtrNode, expectedMethodAddr != 0,
                    "method test method pointer constant is null");

                TR::Node *classNode = vtableEntryNode->getFirstChild();
                TR::VPConstraint *classConstraint = vp->getConstraint(classNode, isGlobal);

                TR_OpaqueMethodBlock *expectedMethodPtr = reinterpret_cast<TR_OpaqueMethodBlock *>(expectedMethodAddr);

                TR_ResolvedMethod *expectedMethod
                    = vp->comp()->fe()->createResolvedMethod(vp->comp()->trMemory(), expectedMethodPtr);

                TR_OpaqueClassBlock *expectedClass = expectedMethod->containingClass();

                TR_YesNoMaybe taken = TR_maybe;
                const char *foldReason = NULL;
                if (classConstraint != NULL && classConstraint->isClassObject() == TR_yes
                    && classConstraint->getClass() != NULL) {
                    TR_OpaqueClassBlock *clazz = classConstraint->getClass();
                    int32_t vftOffset = static_cast<int32_t>(entrySR->getOffset());
                    // NOTE: clazz might not have a large enough VFT, in which
                    // case vftEntry will be 0. This can happen:
                    // - when the guard is for an interface call,
                    // - when the guard is on a dead path, or
                    // - maybe when the guard has been hoisted.
                    intptr_t vftEntry = TR::Compiler->cls.getVFTEntry(vp->comp(), clazz, vftOffset);

                    // We know the receiver is an instance of clazz, and if we
                    // successfully found a VFT entry, then clazz has a large
                    // enough VFT, so the VFT entry load will be in-bounds.
                    if (vftEntry != 0
                        && performTransformation(vp->comp(), "%sSetting vftEntryIsInBounds on method test n%un [%p]\n",
                            OPT_DETAILS, node->getGlobalIndex(), node)) {
                        node->setVFTEntryIsInBounds(true);
                    }

                    if (ignoreVirtualGuard && classConstraint->isFixedClass()) {
                        // We know the exact type of the receiver and therefore
                        // the exact result of the comparison.
                        //
                        // The case where clazz doesn't have a VFT entry at the
                        // expected offset doesn't need to be handled specially.
                        // In that case we should behave as though vftEntry is
                        // not the expected method, which will be the natural
                        // result of the comparison because vftEntry == 0 but
                        // expectedMethodAddr != 0 (checked in the assertion above).
                        //
                        taken = (vftEntry == expectedMethodAddr) ? TR_no : TR_yes;
                        foldReason = "fixed-type receiver";
                    } else if (ignoreVirtualGuard) {
                        bool fixedObjectType = false;
                        bool fixedCastType = true;
                        TR_YesNoMaybe isInstance
                            = vp->comp()->fe()->isInstanceOf(clazz, expectedClass, fixedObjectType, fixedCastType);

                        if (isInstance == TR_no) {
                            taken = TR_yes; // necessarily unequal
                            foldReason = "incompatible receiver";
                        } else if (vftEntry == expectedMethodAddr && expectedMethod->isFinal()) {
                            taken = TR_no; // necessarily equal
                            foldReason = "type bound has final inlined method";
                        } else if (vftEntry == expectedMethodAddr && node->isProfiledGuard()
                            && !expectedMethod->virtualMethodIsOverridden() && node->getOpCodeValue() == TR::ifacmpne
                            && upgradeToNopGuard(vp, node, TR_NonoverriddenGuard)) {
                            return node;
                        }
                    }
                }

                if (taken != TR_maybe && vp->trace()) {
                    traceMsg(vp->comp(), "n%un [%p]: method test: %s: branch %s taken\n", node->getGlobalIndex(), node,
                        foldReason, taken == TR_yes ? "is" : "is not");
                }

                if (taken == TR_yes) {
                    cannotFallThrough = true;
                } else if (taken == TR_no) {
                    cannotBranch = true;
                } else {
                    // We can't fold, so at least learn a type bound (i.e.
                    // expectedClass) for objects that pass the guard. Note
                    // that instanceofObjectRef here is actually the VFT,
                    // which will be accounted for and possibly changed just
                    // after the switch.
                    instanceofObjectRef = classNode;
                    guardClassConstraint = TR::VPClass::create(vp, TR::VPResolvedClass::create(vp, expectedClass),
                        TR::VPNonNullObject::create(vp), NULL, NULL,
                        TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject));
                }
            } break;

            default:
                break;
        }

        if (guardClassConstraint != NULL && guardClassConstraint->getClassType() != NULL && instanceofObjectRef != NULL
            && !cannotBranch && !cannotFallThrough) {
            instanceofOnBranch = false; // type bound is on the fallthrough edge
            instanceofConstraint = guardClassConstraint;
            instanceofDetectedAndFixedType = virtualGuard->getTestType() == TR_VftTest;

            if (vp->trace()) {
                int32_t sigLen = 0;
                const char *sig = guardClassConstraint->getClassSignature(sigLen);
                traceMsg(vp->comp(), "n%un [%p]: guard only accepts instances of %s%.*s\n", node->getGlobalIndex(),
                    node, instanceofDetectedAndFixedType ? "(exactly) " : "", sigLen, sig);
            }

            instanceofObjectRefIsVft = true;
            if (instanceofObjectRef->getOpCodeValue() == TR::aloadi
                && instanceofObjectRef->getSymbolReference() == vp->comp()->getSymRefTab()->findVftSymbolRef()) {
                instanceofObjectRef = instanceofObjectRef->getFirstChild();
                instanceofObjectRefIsVft = false;
            }
        }
    }

    // Avoid analyzing the virtual call path if we know that the call will
    // will be devirtualized. Devirtualization only happens late in VP
    // as this affects CFG which cannot change while analysis is in progress.
    // This code would help in avoiding the virtual call path when creating constraints
    // for a return value (for example) from a guarded virtual call
    //
    bool virtualGuardWillBeEliminated = false;
    if (node->isTheVirtualGuardForAGuardedInlinedCall() && node->isNonoverriddenGuard()) {
        TR::Node *callNode = node->getVirtualCallNodeForGuard();

        if (callNode && callNode->getOpCode().isCall()) {
            // Calling canFoldNonOverriddenGuard has the potential side effect of creating global
            // constraints for definitions used by the call that reach the conditional branch that is
            // under consideration, without taking into account definitions that might reach the call
            // on other paths.  In order to decide correctly whether the guard can be eliminated and
            // ensure any global constraints will be created correctly, this needs to check that the
            // only path to the call must pass through the current branch.  The easiest way to do
            // that is to check that the target of the branch is the call block itself and that the
            // call block has no other predecessors, though that will fail to optimize any case where
            // the call is reached by a sequence of branches from the target of the branch, each of
            // which has a single predecessor.
            //
            if (target->getPredecessors().size() == 1) {
                TR::Block *callBlock = node->getVirtualCallTreeForGuard()->getEnclosingBlock();

                if (callBlock == target) {
                    virtualGuardWillBeEliminated = canFoldNonOverriddenGuard(vp, callNode, node);
                }
            }

            if (!virtualGuardWillBeEliminated && vp->lastTimeThrough() && vp->_isGlobalPropagation) {
                dumpOptDetails(vp->comp(), "Saving guard %p for call %p\n", node, callNode);
                TR_Pair<TR::TreeTop, TR::CFGEdge> *pair
                    = new (vp->comp()->trHeapMemory()) TR_Pair<TR::TreeTop, TR::CFGEdge>(vp->_curTree, edge);
                List<TR_Pair<TR::TreeTop, TR::CFGEdge> > *&list = (*(vp->_callNodeToGuardNodes))[callNode];

                if (!list)
                    list = new (vp->comp()->trStackMemory())
                        List<TR_Pair<TR::TreeTop, TR::CFGEdge> >(vp->comp()->trMemory());

                list->setRegion(vp->comp()->trMemory()->currentStackRegion());
                list->add(pair);
            } else if (virtualGuardWillBeEliminated) {
                dumpOptDetails(vp->comp(), "Removing guard %p for call %p\n", node, callNode);
            }
        }
    }

    // Apply new constraints to the branch edge
    //
    if (branchOnEqual) {
        if (lhs) {
            TR::VPConstraint *resultType = NULL;
            int32_t result = 1;
            if (rhs && rhs->getClassType() && lhs->getClassType()) {
                // if intersection fails, result = 0
                vp->checkTypeRelationship(lhs, rhs, result, false, false);
                if (!result) {
                    if (vp->trace())
                        traceMsg(vp->comp(), "   types are inconsistent, result will not be propagated\n");
                }
            }
            if (result && !isVirtualGuardNopable && !vp->addEdgeConstraint(rhsChild, lhs, edgeConstraints)) {
                // propagation of constraints failed
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
        if (rhs) {
            TR::VPConstraint *resultType = NULL;
            int32_t result = 1;
            if (lhs && lhs->getClassType() && rhs->getClassType()) {
                // if intersection fails, result = 0
                vp->checkTypeRelationship(lhs, rhs, result, false, false);
                if (!result) {
                    if (vp->trace())
                        traceMsg(vp->comp(), "   types are inconsistent, result will not be propagated\n");
                }
            }
            if (result && !isVirtualGuardNopable && !vp->addEdgeConstraint(lhsChild, rhs, edgeConstraints)) {
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    } else {
        if (notEqualConstraint) {
            if (!vp->addEdgeConstraint(constraintChild, notEqualConstraint, edgeConstraints)) {
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    }

    if (instanceofConstraint && instanceofOnBranch) {
        TR::VPConstraint *newConstraint = passingTypeTestObjectConstraint(vp, instanceofConstraint,
            instanceofDetectedAndFixedType, instanceofObjectRefIsVft);

        if (!isVirtualGuardNopable && !vp->addEdgeConstraint(instanceofObjectRef, newConstraint, edgeConstraints)) {
            TR_ASSERT_FATAL(!vp->intersectionFailed(), "VP intersection error");
            cannotBranch = true;
        }
    }

    // Apply relative constraint to the branch edge
    //
    // Do not create the (in)equality constraint on object comparison.  There is no
    // intrinsic value of the comparison, and instead we end with way too many objects
    // related together because they were all related to null.  This increases the time
    // it takes to propagate constraints, but provides no useful value since we track
    // (non-)nullness using a different mechanism anyway.
    //
    if (node->getOpCodeValue() != TR::ifacmpeq && node->getOpCodeValue() != TR::ifacmpne) {
        if (branchOnEqual) {
            if (!vp->addEdgeConstraint(lhsChild, TR::VPEqual::create(vp, 0), edgeConstraints, rhsChild)) {
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        } else {
            if (!vp->addEdgeConstraint(lhsChild, TR::VPNotEqual::create(vp, 0), edgeConstraints, rhsChild)) {
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    }

    if (vp->trace() && !cannotBranch)
        vp->printEdgeConstraints(edgeConstraints);

    // Apply new constraints to the fall through edge
    //
    if (branchOnEqual) {
        if (notEqualConstraint) {
            if (!vp->addBlockConstraint(constraintChild, notEqualConstraint, NULL, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    } else {
        if (lhs) {
            TR::VPConstraint *resultType = NULL;
            int32_t result = 1;
            if (rhs && rhs->getClassType() && lhs->getClassType()) {
                // if intersection fails, result = 0
                vp->checkTypeRelationship(lhs, rhs, result, false, false);
                if (!result) {
                    if (vp->trace())
                        traceMsg(vp->comp(), "   types are inconsistent, result will not be propagated\n");
                }
            }

            if (result && !isVirtualGuardNopable && !vp->addBlockConstraint(rhsChild, lhs, NULL, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
        if (rhs) {
            TR::VPConstraint *resultType = NULL;
            int32_t result = 1;
            if (lhs && lhs->getClassType() && rhs->getClassType()) {
                // if intersection fails, result = 0
                vp->checkTypeRelationship(lhs, rhs, result, false, false);
                if (!result) {
                    if (vp->trace())
                        traceMsg(vp->comp(), "   types are inconsistent, result will not be propagated\n");
                }
            }

            if (result && !isVirtualGuardNopable && !vp->addBlockConstraint(lhsChild, rhs, NULL, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    }

    if (instanceofConstraint && !instanceofOnBranch) {
        TR::VPConstraint *newConstraint = passingTypeTestObjectConstraint(vp, instanceofConstraint,
            instanceofDetectedAndFixedType, instanceofObjectRefIsVft);

        if (!isVirtualGuardNopable && !vp->addBlockConstraint(instanceofObjectRef, newConstraint, NULL, false)) {
            TR_ASSERT_FATAL(!vp->intersectionFailed(), "VP intersection error");
            cannotFallThrough = true;
        }
    }

    if (virtualGuardWillBeEliminated)
        cannotBranch = true;

    if (node->getOpCodeValue() != TR::ifacmpeq && node->getOpCodeValue() != TR::ifacmpne) {
        // Apply relative constraint to the fall through edge
        //
        if (branchOnEqual) {
            if (!vp->addBlockConstraint(lhsChild, TR::VPNotEqual::create(vp, 0), rhsChild, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        } else {
            if (!vp->addBlockConstraint(lhsChild, TR::VPEqual::create(vp, 0), rhsChild, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    }

    if (cannotBranch
        && performTransformation(vp->comp(), "%sRemoving conditional branch [%p] %s\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        TR_ASSERT(!cannotFallThrough, "Cannot branch or fall through");
        removeConditionalBranch(vp, node, edge);
    } else if (cannotFallThrough
        && performTransformation(vp->comp(), "%sChanging node [%p] %s into goto\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        TR_ASSERT(!cannotBranch, "Cannot branch or fall through");
        changeConditionalToGoto(vp, node, edge);
    }

    return node;
}

TR::Node *constrainIfcmpeq(OMR::ValuePropagation *vp, TR::Node *node) { return constrainIfcmpeqne(vp, node, true); }

TR::Node *constrainIfcmpne(OMR::ValuePropagation *vp, TR::Node *node) { return constrainIfcmpeqne(vp, node, false); }

void getLimits(OMR::ValuePropagation *vp, int32_t *zLow, int32_t *zHigh, TR::Node *symNode, bool isGlobal)
{
    TR::VPConstraint *zConstraint = vp->getConstraint(symNode, isGlobal);

    *zLow = static_cast<int32_t>(TR::getMinSigned<TR::Int32>());
    *zHigh = static_cast<int32_t>(TR::getMaxSigned<TR::Int32>());

    if (zConstraint) {
        TR::VPIntRange *zRange = zConstraint->asIntRange();
        if (zRange) {
            *zLow = zRange->getLow();
            *zHigh = zRange->getHigh();
        }
    }
}

void getLimits(OMR::ValuePropagation *vp, int64_t *zLow, int64_t *zHigh, TR::Node *symNode, bool isGlobal)
{
    TR::VPConstraint *zConstraint = vp->getConstraint(symNode, isGlobal);

    *zLow = TR::getMinSigned<TR::Int64>();
    *zHigh = TR::getMaxSigned<TR::Int64>();

    if (zConstraint) {
        TR::VPLongRange *zRange = zConstraint->asLongRange();
        if (zRange) {
            *zLow = zRange->getLowLong();
            *zHigh = zRange->getHighLong();
        }
    }
}

void getConstValue(int32_t *cConst, TR::Node *constNode) { *cConst = constNode->getInt(); }

void getConstValue(int64_t *cConst, TR::Node *constNode) { *cConst = constNode->getLongInt(); }

void getExtremes(int32_t *lowEnd, int32_t *highEnd)
{
    *lowEnd = static_cast<int32_t>(TR::getMinSigned<TR::Int32>());
    *highEnd = static_cast<int32_t>(TR::getMaxSigned<TR::Int32>());
}

void getExtremes(int64_t *lowEnd, int64_t *highEnd)
{
    *lowEnd = TR::getMinSigned<TR::Int64>();
    *highEnd = TR::getMaxSigned<TR::Int64>();
}

TR::Node *makeNewRhsNode(OMR::ValuePropagation *vp, TR::Node *node, TR::Node *symNodeY, int32_t constDiff)
{
    TR::Node *newBaNode = TR::Node::create(node, TR::iconst, 0, constDiff);
    TR::Node *newRhsNode = TR::Node::create(TR::isub, 2, symNodeY, newBaNode);
    return newRhsNode;
}

TR::Node *makeNewRhsNode(OMR::ValuePropagation *vp, TR::Node *node, TR::Node *symNodeY, int64_t constDiff)
{
    TR::Node *newBaNode = TR::Node::create(node, TR::lconst, 0, 0);
    newBaNode->setLongInt(constDiff);
    TR::Node *newRhsNode = TR::Node::create(TR::lsub, 2, symNodeY, newBaNode);
    return newRhsNode;
}

// Decide and execute simplification:  x + a < y + b  changes into  x < y + (b - a) .
//
template<class IntType>
static TR::Node *simplifyInequality(OMR::ValuePropagation *vp, TR::Node *node, TR::Node *lhsChild, TR::Node *rhsChild,
    bool isGlobal, bool childrenReversed)
{
    // Pattern match * + a, * + b
    // (+ was converted to - in a previous optimization)
    //
    if ((lhsChild->getOpCode().isAdd() || lhsChild->getOpCode().isSub())
        && (rhsChild->getOpCode().isAdd() || rhsChild->getOpCode().isSub())
        && lhsChild->getSecondChild()->getOpCode().isLoadConst()
        && rhsChild->getSecondChild()->getOpCode().isLoadConst()) {
        // Get ranges and constants
        //
        IntType xLow, xHigh;
        IntType yLow, yHigh;

        getLimits(vp, &xLow, &xHigh, lhsChild->getFirstChild(), isGlobal);
        getLimits(vp, &yLow, &yHigh, rhsChild->getFirstChild(), isGlobal);

        IntType aConst;
        IntType bConst;
        IntType cConst;

        getConstValue(&aConst, lhsChild->getSecondChild());
        getConstValue(&bConst, rhsChild->getSecondChild());

        if (lhsChild->getOpCode().isSub())
            aConst = -aConst;
        if (rhsChild->getOpCode().isSub())
            bConst = -bConst;

        IntType low, high;

        getExtremes(&low, &high);

        // Compute overflow conditions
        //
        if ( // no overflow in x + a
            (aConst > 0 ? xHigh <= high - aConst : xLow >= low - aConst) &&
            // no overflow in y + b
            (bConst > 0 ? yHigh <= high - bConst : yLow >= low - bConst) &&
            // no overflow in b - a
            (aConst < 0 ? bConst <= high + aConst : bConst >= low + aConst) &&
            // no overflow in y + (b - a)
            (aConst > bConst ? yLow >= low + aConst - bConst : yHigh <= high - bConst + aConst)) {
            // Execute optimization
            //
            TR::Node *newLhsNode = lhsChild->getFirstChild();

            cConst = aConst - bConst;

            TR::Node *newRhsNode = makeNewRhsNode(vp, node, rhsChild->getFirstChild(), cConst);

            if (childrenReversed) {
                node->setAndIncChild(0, newRhsNode);
                node->setAndIncChild(1, newLhsNode);
            } else {
                node->setAndIncChild(0, newLhsNode);
                node->setAndIncChild(1, newRhsNode);
            }

            rhsChild->recursivelyDecReferenceCount();
            lhsChild->recursivelyDecReferenceCount();
            lhsChild = newLhsNode;
            rhsChild = newRhsNode;

            // A newly-created node may have a value number larger than the limit (e.g. 200,000 for WCode).
            // Values larger than that limit are used for other purposes, so there can't be any overlap. It's not safe
            // to call constrainChildren on a newly-created node
            // constrainChildren(vp, node);
        }
    }
    return node;
}

static int64_t clippedDifference(int64_t left, int64_t right)
{
    TR_ASSERT(left >= right, "clippedDifference expects left >= right");

    // If left is positive and right is negative, left-right could overflow, and
    // what should be a large positive difference could end up negative.  This
    // function's purpose is to returns a value X with the property that the
    // expression "X > Y" always returns the same result as the ideal un-bounded
    // mathematical expression "(left-right) > Y" would have returned,
    // for Y within the range TR::getMinSigned<TR::Int32>() <= Y <= -TR::getMinSigned<TR::Int32>(). (Note:
    // -TR::getMinSigned<TR::Int32>() = TR::getMaxSigned<TR::Int32>()+1)
    //
    // Because left >= right, it is impossible for (left-right) to be negative,
    // so a negative difference unambiguously indicates an overflow, and we
    // instead return TR::getMaxSigned<TR::Int64>() to meet the above requirement.
    //
    // (Note that overflow in the negative direction can't occur when left >= right.)

    if (left - right < 0)
        return TR::getMaxSigned<TR::Int64>();
    else
        return left - right;
}

// Handles ificmplt, ificmple, ificmpgt, ificmpge
//
static TR::Node *constrainIfcmplessthan(OMR::ValuePropagation *vp, TR::Node *node, TR::Node *lhsChild,
    TR::Node *rhsChild, bool orEqual)
{
    bool isUnsigned = node->getOpCode().isUnsignedCompare();

    // Note that if the comparison is gt or ge, the children have been swapped so
    // that we can do a lt or le comparison
    //
    bool childrenReversed = (rhsChild == node->getFirstChild()); // MUST be before constrainChildren
    constrainChildren(vp, node);

    // Find the output edge from the current block that corresponds to this
    // branch
    //
    TR::Block *target = node->getBranchDestination()->getNode()->getBlock();
    TR::Block *fallThrough = vp->_curBlock->getNextBlock();
    // check for target and fall-through edges being the same
    if (fallThrough == target)
        return node;

    // it's possible the children have been updated.  Reset rhs & lhs in case
    if (childrenReversed) {
        lhsChild = node->getSecondChild();
        rhsChild = node->getFirstChild();
    } else {
        lhsChild = node->getFirstChild();
        rhsChild = node->getSecondChild();
    }

    TR::CFGEdge *edge = vp->findOutEdge(vp->_curBlock->getSuccessors(), target);

    bool isGlobal;
    TR::VPConstraint *lhs = NULL;
    TR::VPConstraint *rhs = NULL;
    TR::VPConstraint *rel = NULL;

    bool cannotBranch = false;
    bool cannotFallThrough = false;

    // Quick check for the same value number
    //
    if (vp->getValueNumber(lhsChild) == vp->getValueNumber(rhsChild)) {
        if (orEqual)
            cannotFallThrough = true;
        else
            cannotBranch = true;
    }

    // See if there are absolute constraints on the children
    //
    else {
        lhs = vp->getConstraint(lhsChild, isGlobal);
        rhs = vp->getConstraint(rhsChild, isGlobal);

        if (lhsChild->getOpCode().isLong() && !isUnsigned) {
            simplifyInequality<int64_t>(vp, node, lhsChild, rhsChild, isGlobal, childrenReversed);
        } else if (!isUnsigned) {
            simplifyInequality<int32_t>(vp, node, lhsChild, rhsChild, isGlobal, childrenReversed);
        }

        if (lhs && rhs && !isUnsigned) {
            if (orEqual) {
                if (lhs->mustBeLessThanOrEqual(rhs, vp))
                    cannotFallThrough = true;
                else if (rhs->mustBeLessThan(lhs, vp))
                    cannotBranch = true;
            } else {
                if (lhs->mustBeLessThan(rhs, vp))
                    cannotFallThrough = true;
                else if (rhs->mustBeLessThanOrEqual(lhs, vp))
                    cannotBranch = true;
            }
        }
    }

    // See if there are relative constraints on the children
    //
    if (!cannotBranch && !cannotFallThrough && !isUnsigned) {
        rel = vp->getConstraint(lhsChild, isGlobal, rhsChild);
        if (rel) {
            if (orEqual) {
                if (rel->mustBeLessThanOrEqual())
                    cannotFallThrough = true;
                else if (rel->mustBeGreaterThan())
                    cannotBranch = true;
            } else {
                if (rel->mustBeLessThan())
                    cannotFallThrough = true;
                else if (rel->mustBeGreaterThanOrEqual())
                    cannotBranch = true;
            }

            if (rel->asEqual() && !cannotFallThrough && !cannotBranch && (lhs || rhs)) {
                // If we know lhs == rhs+k, and rhs+k can't overflow, we can tell which way the comparison will go.
                // Likewise, since lhs-k == rhs, if we know lhs-k can't overflow, we're also good to go.

                TR::VPEqual *relation = rel->asEqual();
                int32_t increment = relation->increment();

                int64_t maxIncrement = 0; // If absIncrement is more than this, overflow may occur, and (for example)
                                          // a==b+k does not imply a>b
                int64_t absIncrement = increment;
                if (absIncrement < 0) {
                    absIncrement = -absIncrement;
                    if (rhs) {
                        if (rhsChild->getOpCode().isLong())
                            maxIncrement = clippedDifference(rhs->getLowLong(), TR::getMinSigned<TR::Int64>());
                        else
                            maxIncrement = clippedDifference(rhs->getLowInt(), TR::getMinSigned<TR::Int32>());
                    } else {
                        if (lhsChild->getOpCode().isLong())
                            maxIncrement = clippedDifference(TR::getMaxSigned<TR::Int64>(), lhs->getHighLong());
                        else
                            maxIncrement = clippedDifference(TR::getMaxSigned<TR::Int32>(), lhs->getHighInt());
                    }
                } else {
                    if (rhs) {
                        if (rhsChild->getOpCode().isLong())
                            maxIncrement = clippedDifference(TR::getMaxSigned<TR::Int64>(), rhs->getHighLong());
                        else
                            maxIncrement = clippedDifference(TR::getMaxSigned<TR::Int32>(), rhs->getHighInt());
                    } else {
                        if (lhsChild->getOpCode().isLong())
                            maxIncrement = clippedDifference(lhs->getLowLong(), TR::getMinSigned<TR::Int64>());
                        else
                            maxIncrement = clippedDifference(lhs->getLowInt(), TR::getMinSigned<TR::Int32>());
                    }
                }

                if (vp->trace()) {
                    traceMsg(vp->comp(),
                        "   Conditional relation check on %s [%p]: increment=%d, absIncrement=" INT64_PRINTF_FORMAT
                        ", maxIncrement=" INT64_PRINTF_FORMAT ", orEqual=%s\n",
                        node->getOpCode().getName(), node, increment, absIncrement, maxIncrement,
                        orEqual ? "true" : "false");
                }

                // At this point we have: TR::getMinSigned<TR::Int32>() <= absIncrement <=
                // -TR::getMinSigned<TR::Int32>(), and maxIncrement comes from clippedDifference, so by the definition
                // of clippedDifference, we can compare them with the expected results.
                //
                if (absIncrement <= maxIncrement) {
                    // We're in luck.  rhs + increment can't overflow, so now we can
                    // tell how the comparison will go.

                    if (orEqual) {
                        if (increment <= 0)
                            cannotFallThrough = true;
                        else
                            cannotBranch = true;
                    } else {
                        if (increment < 0)
                            cannotFallThrough = true;
                        else
                            cannotBranch = true;
                    }
                }

                if (cannotBranch || cannotFallThrough)
                    if (!performTransformation(vp->comp(), "%sSuccessful conditional relation check on %s [%p]\n",
                            OPT_DETAILS, node->getOpCode().getName(), node)) {
                        cannotBranch = false;
                        cannotFallThrough = false;
                    }
            }
        }
    }

    if (cannotBranch
        && performTransformation(vp->comp(), "%sRemoving conditional branch [%p] %s\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        removeConditionalBranch(vp, node, edge);
        return node;
    }

    if (cannotFallThrough
        && performTransformation(vp->comp(), "%sChanging node [%p] %s into goto\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        vp->printEdgeConstraints(vp->createEdgeConstraints(edge, false));
        changeConditionalToGoto(vp, node, edge);
        return node;
    }

    // Propagate current constraints to the branch target
    //
    if (vp->trace())
        traceMsg(vp->comp(), "   Conditional branch\n");
    OMR::ValuePropagation::EdgeConstraints *edgeConstraints = vp->createEdgeConstraints(edge, true);

    // Find extra constraints to apply to the two edges
    //
    int32_t adjustment = orEqual ? 0 : 1;
    int32_t intBoundary;
    int64_t longBoundary;

    int32_t minIntValue
        = /*isUnsigned ? TR::getMinUnsigned<TR::Int32>() :*/ static_cast<int32_t>(TR::getMinSigned<TR::Int32>());
    int32_t maxIntValue
        = /*isUnsigned ? TR::getMaxUnsigned<TR::Int32>() :*/ static_cast<int32_t>(TR::getMaxSigned<TR::Int32>());
    int64_t minLongValue = isUnsigned ? TR::getMinUnsigned<TR::Int64>() : TR::getMinSigned<TR::Int64>();

    if (lhsChild->getOpCode().isLong()) {
        // Create extra constraints for lhs and rhs on the branch edge
        //
        if (lhs)
            longBoundary = lhs->getLowLong();
        else
            longBoundary = TR::getMinSigned<TR::Int64>();
        longBoundary += adjustment;
        if (longBoundary != TR::getMinSigned<TR::Int64>() && !isUnsigned) {
            if (!vp->addEdgeConstraint(rhsChild,
                    TR::VPLongRange::create(vp, longBoundary, TR::getMaxSigned<TR::Int64>()), edgeConstraints)) {
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }

        if (rhs)
            longBoundary = rhs->getHighLong();
        else
            longBoundary = TR::getMaxSigned<TR::Int64>();
        longBoundary -= adjustment;
        if (isUnsigned && longBoundary < 0)
            longBoundary = TR::getMaxSigned<TR::Int64>();
        if (longBoundary != TR::getMaxSigned<TR::Int64>()) {
            if (!vp->addEdgeConstraint(lhsChild, TR::VPLongRange::create(vp, minLongValue, longBoundary),
                    edgeConstraints)) {
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    } else if (!isUnsigned) {
        // Create extra constraints for lhs and rhs on the branch edge
        //
        if (lhs)
            intBoundary = lhs->getLowInt();
        else
            intBoundary = minIntValue;
        intBoundary += adjustment;
        bool modifiedBoundary = /*isUnsigned ? ((uint32_t)intBoundary != TR::getMinUnsigned<TR::Int32>())
                                  :*/
            (intBoundary != TR::getMinSigned<TR::Int32>());
        // if (intBoundary != TR::getMinSigned<TR::Int32>())
        if (modifiedBoundary) {
            TR::VPConstraint *constraint = TR::VPIntRange::create(vp, intBoundary, maxIntValue /*, isUnsigned*/);
            // if (!vp->addEdgeConstraint(rhsChild, TR::VPIntRange::create(vp, intBoundary,
            // TR::getMaxSigned<TR::Int32>()), edgeConstraints))
            if (!vp->addEdgeConstraint(rhsChild, constraint, edgeConstraints)) {
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }

        if (rhs)
            intBoundary = rhs->getHighInt();
        else
            intBoundary = maxIntValue;
        intBoundary -= adjustment;
        modifiedBoundary = /*isUnsigned ? ((uint32_t)intBoundary != TR::getMaxUnsigned<TR::Int32>())
                             :*/
            (intBoundary != TR::getMaxSigned<TR::Int32>());
        // if (intBoundary != TR::getMaxSigned<TR::Int32>())
        if (modifiedBoundary) {
            TR::VPConstraint *constraint = TR::VPIntRange::create(vp, minIntValue, intBoundary /*, isUnsigned*/);
            // if (!vp->addEdgeConstraint(lhsChild, TR::VPIntRange::create(vp, TR::getMinSigned<TR::Int32>(),
            // intBoundary), edgeConstraints))
            if (!vp->addEdgeConstraint(lhsChild, constraint, edgeConstraints)) {
                if (!vp->intersectionFailed())
                    cannotBranch = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    }

    // Apply relative constraint to the branch edge
    //
    // TR::VPLessThanOrEqual *newConstraint = TR::VPLessThanOrEqual::create(vp, -adjustment);
    // if (newConstraint->increment() != 0)
    //   newConstraint->setHasArtificialIncrement();
    // if (!vp->addEdgeConstraint(lhsChild, newConstraint, edgeConstraints, rhsChild))
    //   cannotBranch = true;

    if (vp->trace() && !cannotBranch)
        vp->printEdgeConstraints(edgeConstraints);

    if (lhsChild->getOpCode().isLong() && !isUnsigned) {
        // Create extra constraints for lhs and rhs on the fall through edge
        //
        if (lhs)
            longBoundary = lhs->getHighLong();
        else
            longBoundary = TR::getMaxSigned<TR::Int64>();
        longBoundary += adjustment - 1;
        if (longBoundary != TR::getMaxSigned<TR::Int64>()) {
            if (!vp->addBlockConstraint(rhsChild,
                    TR::VPLongRange::create(vp, TR::getMinSigned<TR::Int64>(), longBoundary), NULL, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }

        if (rhs)
            longBoundary = rhs->getLowLong();
        else
            longBoundary = TR::getMinSigned<TR::Int64>();
        longBoundary += 1 - adjustment;
        if (longBoundary != TR::getMinSigned<TR::Int64>()) {
            if (!vp->addBlockConstraint(lhsChild,
                    TR::VPLongRange::create(vp, longBoundary, TR::getMaxSigned<TR::Int64>()), NULL, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    } else if (!isUnsigned) {
        // Create extra constraints for lhs and rhs on the fall through edge
        //
        if (lhs)
            intBoundary = lhs->getHighInt();
        else
            intBoundary = maxIntValue;
        intBoundary += adjustment - 1;
        bool modifiedBoundary = /*isUnsigned ? ((uint32_t)intBoundary != TR::getMaxUnsigned<TR::Int32>())
                                             :*/
            (intBoundary != TR::getMaxSigned<TR::Int32>());
        // if (intBoundary != TR::getMaxSigned<TR::Int32>())
        if (modifiedBoundary) {
            TR::VPConstraint *constraint = TR::VPIntRange::create(vp, minIntValue, intBoundary /*, isUnsigned*/);
            // if (!vp->addBlockConstraint(rhsChild, TR::VPIntRange::create(vp, TR::getMinSigned<TR::Int32>(),
            // intBoundary), NULL, false))
            if (!vp->addBlockConstraint(rhsChild, constraint, NULL, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }

        if (rhs)
            intBoundary = rhs->getLowInt();
        else
            intBoundary = minIntValue;
        intBoundary += 1 - adjustment;
        // if (intBoundary != TR::getMinSigned<TR::Int32>())
        modifiedBoundary = /*isUnsigned ? ((uint32_t)intBoundary != TR::getMinUnsigned<TR::Int32>())
                             :*/
            (intBoundary != TR::getMinSigned<TR::Int32>());
        if (modifiedBoundary) {
            TR::VPConstraint *constraint = TR::VPIntRange::create(vp, intBoundary, maxIntValue /*, isUnsigned*/);
            // if (!vp->addBlockConstraint(lhsChild, TR::VPIntRange::create(vp, intBoundary,
            // TR::getMaxSigned<TR::Int32>()), NULL, false))
            if (!vp->addBlockConstraint(lhsChild, constraint, NULL, false)) {
                if (!vp->intersectionFailed())
                    cannotFallThrough = true;
                else
                    vp->setIntersectionFailed(false);
            }
        }
    }

    // Apply relative constraint to the fall through edge
    //
    // FIXME: Now disable the long case
    if (!lhsChild->getOpCode().isLong() && !rhsChild->getOpCode().isLong()) {
        // TR::VPGreaterThanOrEqual *newConstraint = TR::VPGreaterThanOrEqual::create(vp, 1-adjustment);
        // if (newConstraint->increment() != 0)
        //    newConstraint->setHasArtificialIncrement();

        // if (!vp->addBlockConstraint(lhsChild, newConstraint, rhsChild, false))
        //    cannotFallThrough = true;
    }

    if (cannotBranch
        && performTransformation(vp->comp(), "%sRemoving conditional branch [%p] %s\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        TR_ASSERT(!cannotFallThrough, "Cannot branch or fall through");
        removeConditionalBranch(vp, node, edge);
    } else if (cannotFallThrough
        && performTransformation(vp->comp(), "%sChanging node [%p] %s into goto\n", OPT_DETAILS, node,
            node->getOpCode().getName())) {
        TR_ASSERT(!cannotBranch, "Cannot branch or fall through");
        changeConditionalToGoto(vp, node, edge);
    }
    return node;
}

TR::Node *constrainIfcmplt(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainIfcmplessthan(vp, node, node->getFirstChild(), node->getSecondChild(), false);
}

TR::Node *constrainIfcmple(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainIfcmplessthan(vp, node, node->getFirstChild(), node->getSecondChild(), true);
}

TR::Node *constrainIfcmpgt(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainIfcmplessthan(vp, node, node->getSecondChild(), node->getFirstChild(), false);
}

TR::Node *constrainIfcmpge(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainIfcmplessthan(vp, node, node->getSecondChild(), node->getFirstChild(), true);
}

TR::Node *constrainCondBranch(OMR::ValuePropagation *vp, TR::Node *node)
{
    // TODO - handle the special cases for conditional branches:
    //    integer compares, long compares, ref compares
    //

    constrainChildren(vp, node);

    // Put the current list of block constraints on to the edge
    //
    TR::Block *target = node->getBranchDestination()->getNode()->getBlock();
    if (vp->trace())
        traceMsg(vp->comp(), "   Conditional branch\n");

    // Find the output edge from the current block that corresponds to this
    // branch
    //
    TR::CFGEdge *edge = vp->findOutEdge(vp->_curBlock->getSuccessors(), target);
    vp->printEdgeConstraints(vp->createEdgeConstraints(edge, true));
    return node;
}

// Handles icmpeq, icmpne, lcmpeq, lcmpne, acmpeq, acmpne
//
static TR::Node *constrainCmpeqne(OMR::ValuePropagation *vp, TR::Node *node, bool testEqual)
{
    constrainChildren(vp, node);

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(node->getFirstChild(), lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(node->getSecondChild(), rhsGlobal);
    lhsGlobal &= rhsGlobal;

    // bool isUnsigned = node->getType().isUnsignedInt();

    int32_t result = -1;
    if (lhs && rhs) {
        if (lhs->mustBeEqual(rhs, vp))
            result = testEqual ? 1 : 0;
        else if (lhs->mustBeNotEqual(rhs, vp))
            result = testEqual ? 0 : 1;
    }

    // If the result is known, replace the compare with the corresponding
    // constant
    //
    TR::VPConstraint *constraint = NULL;
    if (result >= 0) {
        constraint = TR::VPIntConst::create(vp, result /*, isUnsigned*/);
        if (lhsGlobal || vp->lastTimeThrough()) {
            vp->replaceByConstant(node, constraint, lhsGlobal);
            return node;
        }
    } else {
        TR::Node *firstChild = node->getFirstChild();
        TR::Node *secondChild = node->getSecondChild();
        bool lhsIsBool = false;
        if (lhs && vp->lastTimeThrough())
            lhsIsBool = isBoolean(lhs);

        if (secondChild->getOpCode().isLoadConst() && lhsIsBool && vp->getCurrentParent()) {
            int64_t constValue;
            if ((node->getOpCodeValue() == TR::icmpeq) || (node->getOpCodeValue() == TR::icmpne))
                constValue = (int64_t)secondChild->getInt();
            else
                constValue = secondChild->getLongInt();

            if (((constValue == 1)
                    && ((node->getOpCodeValue() == TR::icmpeq) || (node->getOpCodeValue() == TR::lcmpeq)))
                || ((constValue == 0)
                    && ((node->getOpCodeValue() == TR::icmpne) || (node->getOpCodeValue() == TR::lcmpne)))) {
                if (performTransformation(vp->comp(),
                        "%sReduced identity operation on bool in node [" POINTER_PRINTF_FORMAT "] \n", OPT_DETAILS,
                        node)) {
                    TR::Node *parent = vp->getCurrentParent();
                    vp->invalidateValueNumberInfo();
                    vp->invalidateUseDefInfo();

                    int32_t i;
                    for (i = parent->getNumChildren() - 1; i >= 0; i--) {
                        if (parent->getChild(i) == node)
                            break;
                    }

                    TR::Node *newChild = firstChild;
                    if (firstChild->getOpCode().isLong())
                        newChild = TR::Node::create(TR::l2i, 1, firstChild);

                    parent->setAndIncChild(i, newChild);
                    node->recursivelyDecReferenceCount();
                }
            } else if (((constValue == 1)
                           && ((node->getOpCodeValue() == TR::icmpne) || (node->getOpCodeValue() == TR::lcmpne)))
                || ((constValue == 0)
                    && ((node->getOpCodeValue() == TR::icmpeq) || (node->getOpCodeValue() == TR::lcmpeq)))) {
                if (firstChild->getOpCodeValue() == node->getOpCodeValue()) {
                    TR::Node *firstGrandChild = firstChild->getFirstChild();
                    TR::Node *secondGrandChild = firstChild->getSecondChild();

                    bool b;
                    TR::VPConstraint *firstGrand = vp->getConstraint(firstGrandChild, b);

                    bool firstGrandIsBool = false;
                    if (firstGrand && vp->lastTimeThrough())
                        firstGrandIsBool = isBoolean(firstGrand);

                    if (secondGrandChild->getOpCode().isLoadConst() && firstGrandIsBool && vp->getCurrentParent()) {
                        int64_t firstGrandConstValue;
                        if ((firstChild->getOpCodeValue() == TR::icmpeq)
                            || (firstChild->getOpCodeValue() == TR::icmpne))
                            firstGrandConstValue = (int64_t)secondGrandChild->getInt();
                        else
                            firstGrandConstValue = secondGrandChild->getLongInt();

                        if (constValue == firstGrandConstValue) {
                            if (performTransformation(vp->comp(),
                                    "%sReduced 2 NOTs of bool in node [" POINTER_PRINTF_FORMAT "] \n", OPT_DETAILS,
                                    node)) {
                                TR::Node *parent = vp->getCurrentParent();
                                vp->invalidateValueNumberInfo();
                                vp->invalidateUseDefInfo();

                                int32_t i;
                                for (i = parent->getNumChildren() - 1; i >= 0; i--) {
                                    if (parent->getChild(i) == node)
                                        break;
                                }

                                TR::Node *newChild = firstGrandChild;
                                if (firstGrandChild->getOpCode().isLong())
                                    newChild = TR::Node::create(TR::l2i, 1, firstGrandChild);

                                parent->setAndIncChild(i, newChild);
                                node->recursivelyDecReferenceCount();
                            }
                        }
                    }
                }
            }
        }

        constraint = TR::VPIntRange::create(vp, 0, 1 /*, isUnsigned*/);
    }

    vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
    return node;
}

TR::Node *constrainCmpeq(OMR::ValuePropagation *vp, TR::Node *node) { return constrainCmpeqne(vp, node, true); }

TR::Node *constrainCmpne(OMR::ValuePropagation *vp, TR::Node *node) { return constrainCmpeqne(vp, node, false); }

// Handles icmplt, icmple, icmpgt, icmpge
//
static TR::Node *constrainCmplessthan(OMR::ValuePropagation *vp, TR::Node *node, TR::Node *lhsChild, TR::Node *rhsChild,
    bool orEqual)
{
    TR_ASSERT(!node->getOpCode().isUnsignedCompare(),
        "This code doesn't handle logical comparisons, but it should be easy to fix it.");

    // Note that if the comparison is gt or ge, the children have been swapped so
    // that we can do a lt or le comparison
    //
    bool childrenReversed = (rhsChild == node->getFirstChild()); // MUST be before constainChildren

    constrainChildren(vp, node);

    // it's possible the children have been updated.  Reset rhs & lhs in case
    if (childrenReversed) {
        lhsChild = node->getSecondChild();
        rhsChild = node->getFirstChild();
    } else {
        lhsChild = node->getFirstChild();
        rhsChild = node->getSecondChild();
    }

    bool lhsGlobal, rhsGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(lhsChild, lhsGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(rhsChild, rhsGlobal);
    lhsGlobal &= rhsGlobal;

    // bool isUnsigned = node->getType().isUnsignedInt();

    int32_t result = -1;
    if (lhs && rhs) {
        if (orEqual) {
            if (lhs->mustBeLessThanOrEqual(rhs, vp))
                result = 1;
            else if (rhs->mustBeLessThan(lhs, vp))
                result = 0;
        } else {
            if (lhs->mustBeLessThan(rhs, vp))
                result = 1;
            else if (rhs->mustBeLessThanOrEqual(lhs, vp))
                result = 0;
        }
    }

    // If the result is known, replace the compare with the corresponding
    // constant
    //
    TR::VPConstraint *constraint = NULL;
    if (result >= 0) {
        if ((lhsGlobal || vp->lastTimeThrough())
            && performTransformation(vp->comp(), "%sChanging node [%p] %s into constant %d\n", OPT_DETAILS, node,
                node->getOpCode().getName(), result)) {
            vp->removeChildren(node);
            TR::Node::recreate(node, TR::iconst);
            node->setInt(result);
            vp->invalidateValueNumberInfo();
            return node;
        }
        constraint = TR::VPIntConst::create(vp, result /*, isUnsigned*/);
    } else
        constraint = TR::VPIntRange::create(vp, 0, 1 /*, isUnsigned*/);

    vp->addBlockOrGlobalConstraint(node, constraint, lhsGlobal);
    return node;
}

TR::Node *constrainCmplt(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainCmplessthan(vp, node, node->getFirstChild(), node->getSecondChild(), false);
}

TR::Node *constrainCmple(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainCmplessthan(vp, node, node->getFirstChild(), node->getSecondChild(), true);
}

TR::Node *constrainCmpgt(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainCmplessthan(vp, node, node->getSecondChild(), node->getFirstChild(), false);
}

TR::Node *constrainCmpge(OMR::ValuePropagation *vp, TR::Node *node)
{
    return constrainCmplessthan(vp, node, node->getSecondChild(), node->getFirstChild(), true);
}

TR::Node *constrainCmp(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    vp->addGlobalConstraint(node, TR::VPIntRange::create(vp, 0, 1));
    return node;
}

TR::Node *constrainFloatCmp(OMR::ValuePropagation *vp, TR::Node *node)
{
    vp->addGlobalConstraint(node, TR::VPIntRange::create(vp, -1, 1));
    return node;
}

TR::Node *constrainSwitch(OMR::ValuePropagation *vp, TR::Node *node)
{
    // Process the switch expression before the switch cases
    //
    TR::Node *myParent = vp->getCurrentParent();
    vp->setCurrentParent(node);
    vp->launchNode(node->getFirstChild(), node, 0);
    vp->setCurrentParent(myParent);

    constrainChildren(vp, node);

    // if something is known about the selector, some
    // of the cases can be removed
    //
    // FIXME: process only lookupswitches for now
    //
    if (node->getOpCodeValue() != TR::table) {
        TR::Node *selector = node->getFirstChild();
        bool isGlobal;
        TR::VPConstraint *constraint = vp->getConstraint(selector, isGlobal);
        bool isInt64 = selector->getType().isInt64();
        int64_t maxCase = LONG_MIN;
        int64_t minCase = LONG_MAX;

        if (!isInt64 && constraint && constraint->asIntConstraint()) {
            // bool isUnsigned = selector->getType().isUnsignedInt();
            int32_t low = constraint->asIntConstraint()->getLow();
            int32_t high = constraint->asIntConstraint()->getHigh();
            minCase = INT_MAX;
            maxCase = INT_MIN;
            bool casesRemoved = false;
            int32_t i;
            for (i = node->getNumChildren() - 1; i > 1; i--) {
                int32_t value = node->getChild(i)->getCaseConstant();
                if (/*!isUnsigned &&*/ ((low < value && high < value) || (low > value && high > value))) {
                    TR::Block *target = node->getChild(i)->getBranchDestination()->getNode()->getBlock();
                    if (vp->trace())
                        traceMsg(vp->comp(), "   Case %d (target %d) is unreachable\n", value, target->getNumber());
                    node->removeChild(i);
                    casesRemoved = true;
                } else {
                    if (minCase > value)
                        minCase = value;
                    if (maxCase < value)
                        maxCase = value;
                }
            }

            // remove the edges and propagate paths as unreachable
            //
            if (casesRemoved) {
                TR_ScratchList<TR::Block> remainingTargets(vp->trMemory());
                for (i = node->getNumChildren() - 1; i > 1; i--) {
                    TR::Block *target = node->getChild(i)->getBranchDestination()->getNode()->getBlock();
                    remainingTargets.add(target);
                }

                TR::Block *defaultTarget = node->getChild(1)->getBranchDestination()->getNode()->getBlock();
                TR::CFGEdge *defaultEdge = vp->findOutEdge(vp->_curBlock->getSuccessors(), defaultTarget);
                for (auto e = vp->_curBlock->getSuccessors().begin(); e != vp->_curBlock->getSuccessors().end(); ++e) {
                    if (((*e) == defaultEdge) || remainingTargets.find(toBlock((*e)->getTo())))
                        continue;
                    vp->setUnreachablePath(*e);
                    vp->_edgesToBeRemoved->add(*e);
                }
            }
            if (low >= minCase && high <= maxCase)
                node->setCannotOverflow(
                    true); // Let evaluator know value range of selector is covered by min and max case
        } // if IntConstraint
        else if (isInt64 && constraint && constraint->asLongConstraint()) {
            // bool isUnsigned = selector->getType().isUnsignedInt();
            int64_t low = constraint->asLongConstraint()->getLow();
            int64_t high = constraint->asLongConstraint()->getHigh();
            bool casesRemoved = false;
            int32_t i;
            for (i = node->getNumChildren() - 1; i > 1; i--) {
                int64_t value = node->getChild(i)->getCaseConstant();
                if (/*!isUnsigned &&*/ ((low < value && high < value) || (low > value && high > value))) {
                    TR::Block *target = node->getChild(i)->getBranchDestination()->getNode()->getBlock();
                    if (vp->trace())
                        traceMsg(vp->comp(), "   Case %d (target %d) is unreachable\n", value, target->getNumber());
                    node->removeChild(i);
                    casesRemoved = true;
                } else {
                    if (minCase > value)
                        minCase = value;
                    if (maxCase < value)
                        maxCase = value;
                }
            }

            // remove the edges and propagate paths as unreachable
            //
            if (casesRemoved) {
                TR_ScratchList<TR::Block> remainingTargets(vp->trMemory());
                for (i = node->getNumChildren() - 1; i > 1; i--) {
                    TR::Block *target = node->getChild(i)->getBranchDestination()->getNode()->getBlock();
                    remainingTargets.add(target);
                }

                TR::Block *defaultTarget = node->getChild(1)->getBranchDestination()->getNode()->getBlock();
                TR::CFGEdge *defaultEdge = vp->findOutEdge(vp->_curBlock->getSuccessors(), defaultTarget);
                for (auto e = vp->_curBlock->getSuccessors().begin(); e != vp->_curBlock->getSuccessors().end(); ++e) {
                    if (((*e) == defaultEdge) || remainingTargets.find(toBlock((*e)->getTo())))
                        continue;
                    vp->setUnreachablePath(*e);
                    vp->_edgesToBeRemoved->add(*e);
                }
            }
            if (low >= minCase && high <= maxCase)
                node->setCannotOverflow(
                    true); // Let evaluator know value range of selector is covered by min and max case
        } // ... else if LongConstraint
    }

    // Fall-through is now unreachable
    //
    vp->setUnreachablePath();

    return node;
}

TR::Node *constrainCase(OMR::ValuePropagation *vp, TR::Node *node)
{
    // Put the current list of block constraints on to the edge
    //
    TR::Block *target = node->getBranchDestination()->getNode()->getBlock();
    if (vp->trace())
        traceMsg(vp->comp(), "   Switch case branch\n");

    // Find the output edge from the current block that corresponds to this
    // branch
    //
    TR::CFGEdge *edge = vp->findOutEdge(vp->_curBlock->getSuccessors(), target);
    vp->printEdgeConstraints(vp->createEdgeConstraints(edge, true));
    return node;
}

static void constrainClassObjectLoadaddr(OMR::ValuePropagation *vp, TR::Node *node, bool isGlobal)
{
    TR_ASSERT(node->getOpCodeValue() == TR::loadaddr, "constrainClassObjectLoadaddr: n%un %s is not a loadaddr\n",
        node->getGlobalIndex(), node->getOpCode().getName());

    TR::SymbolReference *symRef = node->getSymbolReference();

    TR_ASSERT(symRef->getSymbol()->isClassObject(), "constrainClassObjectLoadaddr: n%un loadaddr is not for a class\n",
        node->getGlobalIndex());

    bool isFixed = vp->canClassBeTrustedAsFixedClass(symRef, NULL);

    TR::VPConstraint *constraint
        = TR::VPClass::create(vp, TR::VPClassType::create(vp, symRef, isFixed), TR::VPNonNullObject::create(vp), NULL,
            NULL, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject));

    vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
}

TR::Node *constrainLoadaddr(OMR::ValuePropagation *vp, TR::Node *node)
{
    // If this is loading the address of a class object or the address of a
    // pointer to a class object, the type is the class.
    //
    TR::VPConstraint *constraint = NULL;
    TR::SymbolReference *symRef = node->getSymbolReference();
    TR::Symbol *symbol = symRef->getSymbol();
    if (symbol->isAddressOfClassObject()) {
        // add a ClassObject property
        TR::VPConstraint *constraint = TR::VPClass::create(vp, TR::VPClassType::create(vp, symRef, false, true), NULL,
            NULL, NULL, TR::VPObjectLocation::create(vp, TR::VPObjectLocation::J9ClassObject));
        vp->addGlobalConstraint(node, constraint);
        // vp->addGlobalConstraint(node, TR::VPClassType::create(vp, symRef, false, true));
        //
        vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
    } else if (symbol->isClassObject()) {
        constrainClassObjectLoadaddr(vp, node, true /* isGlobal */);
    }

    else if (symbol->isLocalObject()) {
        TR::VPClassType *typeConstraint = 0;
        TR::AutomaticSymbol *localObj = symbol->castToLocalObjectSymbol();
        symRef = localObj->getClassSymbolReference();
        if (localObj->getOpCodeKind() == TR::New) {
            if (symRef)
                typeConstraint = TR::VPClassType::create(vp, symRef, true);
        } else if (localObj->getOpCodeKind() == TR::anewarray) {
            typeConstraint = TR::VPClassType::create(vp, symRef, true);
            typeConstraint = typeConstraint->getClassType()->getArrayClass(vp);
            if (typeConstraint) {
                if (typeConstraint->getClass() && !typeConstraint->isFixedClass())
                    typeConstraint = TR::VPFixedClass::create(vp, typeConstraint->getClass());
            }
        } else {
            TR_OpaqueClassBlock *clazz = vp->fe()->getClassFromNewArrayType(localObj->getArrayType());
            if (clazz)
                typeConstraint = TR::VPFixedClass::create(vp, clazz);
        }

        if (typeConstraint)
            vp->addGlobalConstraint(node, typeConstraint);
        vp->addGlobalConstraint(node, TR::VPNonNullObject::create(vp));
    } else {
        // Look at the def nodes for this node to see if the value being addressed
        // is known to be null or non-null
        //
        bool isGlobal;
        constraint = vp->mergeDefConstraints(node, vp->AbsoluteConstraint, isGlobal);
        if (constraint) {
            if (constraint->isNullObject())
                node->setPointsToNull(true);
            else if (constraint->isNonNullObject())
                node->setPointsToNonNull(true);
        }
    }
    return node;
}

void constrainNewlyFoldedConst(OMR::ValuePropagation *vp, TR::Node *node, bool isGlobal)
{
    switch (node->getOpCodeValue()) {
        case TR::iconst:
            constrainIntConst(vp, node, isGlobal);
            break;

        case TR::lconst:
            constrainLongConst(vp, node, isGlobal);
            break;

        case TR::aconst:
            constrainAConst(vp, node, isGlobal);
            break;

        case TR::loadaddr:
            if (node->getSymbolReference()->getSymbol()->isClassObject())
                constrainClassObjectLoadaddr(vp, node, isGlobal);
            break;

        default:
            // The symRef on the node might contain a known object index
            // Create a known object constraint for it if so
            if (node->getDataType() == TR::Address && node->getOpCode().hasSymbolReference()
                && node->getSymbolReference()->hasKnownObjectIndex()) {
                addKnownObjectConstraints(vp, node, isGlobal);
            } else if (vp->trace()) {
                traceMsg(vp->comp(), "constrainNewlyFoldedConst does not recognize n%un %s\n", node->getGlobalIndex(),
                    node->getOpCode().getName());
            }
            break;
    }
}

// Perform null check processing (used by TR::NULLCHK and TR::ResolveAndNULLCHK).
// Return 1 if the null check can be removed.
// Return 2 if the null check must be taken.
// Return 0 if the null check may or may not be taken.
//
static int32_t handleNullCheck(OMR::ValuePropagation *vp, TR::Node *node, bool resolveChkToo)
{
    // Find the child containing the reference to be null checked. This is not
    // necessarily the direct child of this node.
    // Also the node to be null checked may have gone away due to earlier
    // optimizations. In this case the null check is redundant and can be
    // removed.
    //
    TR::Node *referenceChild = node->getNullCheckReference();

    if (referenceChild == NULL) {
        constrainChildren(vp, node);
        return 1;
    }

    // Process the reference child.
    //
    vp->launchNode(referenceChild, node, 0);

    // If the reference child has a non-null constraint the null check can be
    // eliminated.
    //
    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(referenceChild, isGlobal);
    if (constraint && constraint->isNonNullObject()) {
        constrainChildren(vp, node);
        return 1;
    }

    // Propagate to the exception edges
    //
    if (!resolveChkToo) {
        /////vp->createExceptionEdgeConstraints(TR::Block::CanCatchNullCheck, vp->createValueConstraint(referenceChild,
        /// vp->AbsoluteConstraint, TR::VPNullObject::create(vp)), node);
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchNullCheck, NULL, node);
    }

    // If the reference child is null everything past this point in the block is
    // dead, since the exception will always be taken.
    //
    if (constraint && constraint->isNullObject()) {
        vp->mustTakeException();
        return 2;
    }

    // After processing the children we can assert that the reference child is
    // now non-null.
    //
    constrainChildren(vp, node);
    // After processing, the referenceChild may no longer exist,
    // for e.g. arraylength is constant propagated
    // in that case, the nullcheck is redundant
    //
    if (!node->getNullCheckReference())
        return 1;

    vp->addBlockConstraint(referenceChild, TR::VPNonNullObject::create(vp));
    return 0;
}

// Perform resolve check processing (used by TR::ResolveCHK and
// TR::ResolveAndNULLCHK).
// Return true if the resolve check can be removed.
//
static bool handleResolveCheck(OMR::ValuePropagation *vp, TR::Node *node, bool nullChkToo)
{
    // Process all children of this node's child. If an exception is going to be
    // raised it will be after these children have been evaluated but before the
    // child node itself is evaluated, so this is the correct point at which to
    // propagate exception information.
    //
    TR::Node *child = node->getFirstChild();
    constrainChildren(vp, child);

    // See if the child has been proven to be resolved at this program point
    // (can't remove the check for a store to a final field)
    //
    if (!child->hasUnresolvedSymbolReference() && !(node->getOpCode().isStore() && child->getSymbol()->isFinal())) {
        // The child has been transformed into a resolved reference. No need
        // for this ResolveCHK any more.
        //
        return true;
    }

    int32_t index = child->getSymbolReference()->getUnresolvedIndex() + vp->_firstUnresolvedSymbolValueNumber;

    // See if the constraint for this unresolved symbol exists. If it does, it
    // means that there has been a resolve check on all paths to this point.
    //
    OMR::ValuePropagation::Relationship *rel = vp->findConstraint(index);
    if (rel) {
        //
        // Could be a 0 or 1 (range)
        //
        // TR_ASSERT(rel->constraint->asIntConst(), "assertion failure");
        //
        if (child->getOpCode().isStore()) {
            if (rel->constraint->asIntConst() && rel->constraint->asIntConst()->getInt() == 1)
                return true;
        } else
            return true;
    }

    // Propagate to the exception edges
    //
    if (nullChkToo)
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchNullCheck | TR::Block::CanCatchResolveCheck, NULL, node);
    else
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchResolveCheck, NULL, node);

    //
    // This symbol can now be marked as resolved
    //
    // Create an int const == 1 if store is seen; subsequent stores dominated by this one
    // can only be removed if this constraint (int const 1) reaches them. This is because seeing
    // a resolvechk for the load is not sufficient (in every case) to remove the
    // resolvechk on the dominated store (if the unresolved field happened to be final, then the
    // store should raise an illegal access error even though the load will not)
    //
    if (child->getOpCode().isStore())
        vp->addConstraintToList(node, index, vp->AbsoluteConstraint, TR::VPIntConst::create(vp, 1),
            &vp->_curConstraints);
    else if (!rel)
        vp->addConstraintToList(node, index, vp->AbsoluteConstraint, TR::VPIntConst::create(vp, 0),
            &vp->_curConstraints);

    return false;
}

TR::Node *constrainAsm(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    vp->createExceptionEdgeConstraints(TR::Block::CanCatchUserThrows, NULL, node);
    return node;
}

TR::Node *constrainNullChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    // Process the reference child and see if the null check can be removed
    //
    int32_t nullCheckResult = handleNullCheck(vp, node, false);

    // Now remove the check if we can
    //
    if (nullCheckResult == 1
        && performTransformation(vp->comp(), "%sRemoving redundant null check node [%p]\n", OPT_DETAILS, node)) {
        TR_ASSERT(node->getReferenceCount() == 0, "NULLCHK has references");
        TR_ASSERT(node->getNumChildren() == 1, "NULLCHK has more than one child");

        // If the child of the nullchk is normally a treetop node, replace the
        // nullchk with that node
        TR::Node *child = node->getFirstChild();
        //
        if (child->getOpCode().isTreeTop()) {
            if (vp->comp()->useCompressedPointers() && child->getOpCode().isStoreIndirect()) {
                TR::Node::recreate(node, TR::treetop);
            } else {
                TR_ASSERT(child->getReferenceCount() == 1, "Treetop child of NULLCHK has other uses");

                // Increment the reference count on the child so that we know it is
                // going to be kept.
                //
                child->setReferenceCount(0);
                vp->_curTree->setNode(child);
            }
        } else {
            TR::Node::recreate(node, TR::treetop);
        }
        vp->setChecksRemoved();
    }
    return node;
}

TR::Node *constrainZeroChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    TR::Node *child = node->getFirstChild();
    bool isGlobal;
    TR::VPConstraint *nonzeroConstraint
        = TR::VPLongRange::create(vp, TR::getMinSigned<TR::Int64>(), -1)
              ->merge(TR::VPLongRange::create(vp, 1, TR::getMaxSigned<TR::Int64>()), vp);

    TR::VPConstraint *childConstraint = vp->getConstraint(child, isGlobal);
    if (childConstraint) {
        TR::VPConstraint *zeroConstraint = TR::VPIntConst::create(vp, 0);
        if (!zeroConstraint->intersect(childConstraint, vp)) {
            // Can't be zero
            if (performTransformation(vp->comp(), "%sRemoving unnecessary %s [%p]\n", OPT_DETAILS,
                    node->getOpCode().getName(), node)) {
                for (int32_t i = 1; i < node->getNumChildren(); i++)
                    node->getChild(i)->recursivelyDecReferenceCount();
                TR::Node::recreate(node, TR::treetop);
                node->setNumChildren(1);
                vp->setChecksRemoved();
            }
        }

        if (!nonzeroConstraint->intersect(childConstraint, vp)) {
            // Can't be nonzero
            if (performTransformation(vp->comp(), "%sRemoving inevitable %s [%p]\n", OPT_DETAILS,
                    node->getOpCode().getName(), node))
                vp->mustTakeException();
        }
    }

    // From here onward we know it's nonzero
    //
    vp->addBlockConstraint(child, nonzeroConstraint);

    return node;
}

TR::Node *constrainResolveChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    // See if the resolve check can be eliminated
    //
    bool removeTheCheck = handleResolveCheck(vp, node, false);

    // Now process the child itself.
    //
    constrainChildren(vp, node);

    // Processing the child could have caused it to be removed
    if (node->getNumChildren() == 0) {
        TR::Node::recreate(node, TR::treetop);
        return node;
    }

    // Processing the child could have caused it to be changed to a resolved
    // symbol (if a call was devirtualized)
    //
    TR::Node *child = node->getFirstChild();
    if (!child->hasUnresolvedSymbolReference() && !(node->getOpCode().isStore() && child->getSymbol()->isFinal()))
        removeTheCheck = true;

    // Now remove the check if we can
    //
    if (removeTheCheck
        && performTransformation(vp->comp(), "%sRemoving redundant resolve check node [%p]\n", OPT_DETAILS, node)) {
        TR_ASSERT(node->getReferenceCount() == 0, "ResolveCHK has references");
        TR_ASSERT(node->getNumChildren() == 1, "ResolveCHK has more than one child");

        // If the child of the check is normally a treetop node, replace the
        // check node with that node
        //
        if (child->getOpCode().isTreeTop()) {
            if (vp->comp()->useCompressedPointers() && child->getOpCode().isStoreIndirect()) {
                TR::Node::recreate(node, TR::treetop);
            } else {
                TR_ASSERT(child->getReferenceCount() == 1, "Treetop child of ResolveCHK has other uses");

                // Increment the reference count on the child so that we know it is
                // going to be kept.
                //
                child->setReferenceCount(0);
                // vp->_curTree->setNode(child);
                node = child;
            }
        } else {
            TR::Node::recreate(node, TR::treetop);
        }
        vp->setChecksRemoved();
    }

    // storage access here, sync region ends
    // memory barrier required at next critical region
    OMR::ValuePropagation::Relationship *syncRel = vp->findConstraint(vp->_syncValueNumber);
    TR::VPSync *sync = NULL;
    if (!removeTheCheck && syncRel && syncRel->constraint)
        sync = syncRel->constraint->asVPSync();
    if (sync && sync->syncEmitted() == TR_yes) {
        vp->addConstraintToList(NULL, vp->_syncValueNumber, vp->AbsoluteConstraint, TR::VPSync::create(vp, TR_maybe),
            &vp->_curConstraints);
        if (vp->trace()) {
            traceMsg(vp->comp(), "Setting syncRequired due to node [%p]\n", node);
        }
    } else {
        if (vp->trace()) {
            if (sync)
                traceMsg(vp->comp(), "syncRequired is already setup at node [%p]\n", node);
            else if (removeTheCheck)
                traceMsg(vp->comp(), "check got removed at node [%p], syncRequired unchanged\n", node);
            else
                traceMsg(vp->comp(), "No sync constraint found at node [%p]!\n", node);
        }
    }

    return node;
}

TR::Node *constrainResolveNullChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    // See if the resolve check can be eliminated
    //
    bool removeTheCheck = handleResolveCheck(vp, node, true);

    // Now process the child itself.
    //
    constrainChildren(vp, node);

    // Processing the child could have caused it to be changed to a resolved
    // symbol (if a call was devirtualized)
    //
    TR::Node *child = node->getFirstChild();
    if (!child->hasUnresolvedSymbolReference() && !(node->getOpCode().isStore() && child->getSymbol()->isFinal()))
        removeTheCheck = true;

    // See if the null check can be eliminated
    //
    if (handleNullCheck(vp, node, !removeTheCheck) == 1) {
        if (removeTheCheck) {
            if (performTransformation(vp->comp(), "%sChanging ResolveAndNULLCHK node into a treetop node [%p]\n",
                    OPT_DETAILS, node)) {
                TR::Node::recreate(node, TR::treetop);
                vp->setChecksRemoved();
            }
        } else {
            if (performTransformation(vp->comp(), "%sChanging ResolveAndNULLCHK node into a ResolveCHK node [%p]\n",
                    OPT_DETAILS, node)) {
                TR::Node::recreate(node, TR::ResolveCHK);
                vp->setChecksRemoved();
            }
        }
    } else if (removeTheCheck) {
        if (performTransformation(vp->comp(), "%sChanging ResolveAndNULLCHK node into a NULLCHK node [%p]\n",
                OPT_DETAILS, node)) {
            TR::Node::recreate(node, TR::NULLCHK);
            node->setSymbolReference(
                vp->comp()->getSymRefTab()->findOrCreateNullCheckSymbolRef(vp->comp()->getMethodSymbol()));
            vp->setChecksRemoved();
        }
    }
    return node;
}

TR::Node *constrainOverflowChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    return node;
}

TR::Node *constrainUnsignedOverflowChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    return node;
}

TR::Node *constrainDivChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    TR::Node *divNode = node->getFirstChild();
    bool removeDivCheck = false;
    bool complexDivCheck = true;

    if (divNode->getOpCode().isDiv() || divNode->getOpCode().isRem()) {
        bool isGlobal;
        TR::VPConstraint *denominator = vp->getConstraint(divNode->getSecondChild(), isGlobal);

        // If the denominator has a range [!= 0]
        //       then remove the div check.
        //
        if (denominator && !denominator->asMergedConstraints()) {
            if (divNode->getType().isInt32()) {
                if (denominator->getLowInt() > 0 || denominator->getHighInt() < 0)
                    removeDivCheck = true;

                if (denominator->getLowInt() > -1 || denominator->getHighInt() < -1)
                    complexDivCheck = false;
            } else if (divNode->getType().isInt64()) {
                if (denominator->getLowLong() > 0 || denominator->getHighLong() < 0)
                    removeDivCheck = true;

                if (denominator->getLowLong() > -1 || denominator->getHighLong() < -1)
                    complexDivCheck = false;
            }
        } else if (denominator) {
            if (!denominator->asMergedConstraints()->getList()->isEmpty()) {
                removeDivCheck = true;
                complexDivCheck = false;
            }

            ListIterator<TR::VPConstraint> it(denominator->asMergedConstraints()->getList());
            TR::VPConstraint *nextConstraint = NULL;
            for (nextConstraint = it.getFirst(); nextConstraint != NULL; nextConstraint = it.getNext()) {
                if (divNode->getType().isInt32()) {
                    if ((nextConstraint->getLowInt() <= 0) && (nextConstraint->getHighInt() >= 0))
                        removeDivCheck = false;

                    if (nextConstraint->getLowInt() <= -1 && nextConstraint->getHighInt() >= -1)
                        complexDivCheck = true;
                } else if (divNode->getType().isInt64()) {
                    if ((nextConstraint->getLowLong() <= 0) && (nextConstraint->getHighLong() >= 0))
                        removeDivCheck = false;

                    if (nextConstraint->getLowLong() <= -1 && nextConstraint->getHighLong() >= -1)
                        complexDivCheck = true;
                }
            }
        }

        TR::VPConstraint *numerator = vp->getConstraint(divNode->getFirstChild(), isGlobal);
        if (numerator) {
            if (divNode->getType().isInt32()) {
                if (numerator->getLowInt() > TR::getMinSigned<TR::Int32>())
                    complexDivCheck = false;
            } else if (divNode->getType().isInt64()) {
                if (numerator->getLowLong() > TR::getMinSigned<TR::Int64>())
                    complexDivCheck = false;
            }
        }
    } else
        removeDivCheck = true;

    if (removeDivCheck
        && performTransformation(vp->comp(), "%sRemoving redundant div check node [%p]\n", OPT_DETAILS, node))
        TR::Node::recreate(node, TR::treetop);
    else {
        if (!complexDivCheck) {
            if (divNode->getOpCode().isDiv() || divNode->getOpCode().isRem())
                divNode->setSimpleDivCheck(true);
        }
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchDivCheck, NULL, node);
    }
    return node;
}

TR::Node *constrainTRT(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);
    vp->createExceptionEdgeConstraints(TR::Block::CanCatchBoundCheck, NULL, node);
    return node;
}

TR::Node *constrainBndChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    // There are two parts to the bounds check, the array size and the
    // index value to be checked.
    //
    TR::Node *sizeNode = node->getFirstChild();
    TR::Node *indexNode = node->getSecondChild();

    bool isGlobal;
    TR::VPConstraint *sizeBefore = vp->getConstraint(sizeNode, isGlobal);
    TR::VPConstraint *index = vp->getConstraint(indexNode, isGlobal);

    // If the index has a range [0  -> less than the lower bound of the size]
    //       then remove the bound check.
    //
    if (sizeBefore && index) {
        if (index->getLowInt() >= 0 && index->getHighInt() < sizeBefore->getLowInt()
            && performTransformation(vp->comp(), "%sRemoving unnecessary bound check node [%p]\n", OPT_DETAILS, node)) {
            // Change this node to a treetop containing the index (which is
            // currently the second child of the bounds check).
            //
            TR::Node::recreate(node, TR::treetop);
            vp->removeNode(sizeNode);
            node->setChild(0, indexNode);
            node->setChild(1, NULL);
            node->setNumChildren(1);
            vp->setChecksRemoved();
            return node;
        }
    }

    bool globalFlag;
    TR::VPConstraint *relativeConstraint = vp->getConstraint(indexNode, globalFlag, sizeNode);
    if (relativeConstraint && relativeConstraint->mustBeLessThan()
        && performTransformation(vp->comp(), "%sRemoving redundant bound check node (subsumed) [%p]\n", OPT_DETAILS,
            node)) {
        // Change this node to a treetop containing the index (which is
        // currently the second child of the bounds check).
        //
        TR::Node::recreate(node, TR::treetop);
        vp->removeNode(sizeNode);
        node->setChild(0, indexNode);
        node->setChild(1, NULL);
        node->setNumChildren(1);
        vp->setChecksRemoved();
        return node;
    }

    if (vp->_enableVersionBlocks && !vp->_disableVersionBlockForThisBlock && vp->lastTimeThrough())
        vp->_bndChecks->add(node);
    // TODO - create a constraint along the exception edge if possible
    //
    vp->createExceptionEdgeConstraints(TR::Block::CanCatchBoundCheck, NULL, node);

    if (sizeNode == indexNode) {
        vp->mustTakeException();
        return node;
    }

    // We now know the BNDCHK has succeeded, and can make some assertions about the index value.
    //    1) Its low bound must be >= 0
    //    2) Its high bound must be (maximum array size-1)
    //
    uint32_t elementSize = 1;
    if (sizeNode->getOpCode().isArrayLength()) {
        elementSize = sizeNode->getArrayStride();
    }

    int32_t minIndex = 0;
    int32_t maxIndex = static_cast<int32_t>(TR::Compiler->om.maxArraySizeInElements(elementSize, vp->comp()) - 1);

    if (sizeBefore)
        maxIndex = std::min(maxIndex, sizeBefore->getHighInt() - 1);

    // Calculate the new constraint for the index value. If it is null, we
    // will definitely fail the bndchk
    //
    TR::VPConstraint *indexConstraint = NULL;
    if (minIndex <= maxIndex) {
        indexConstraint = TR::VPIntRange::create(vp, minIndex, maxIndex);
        if (index)
            indexConstraint = index->intersect(indexConstraint, vp);
    } else
        indexConstraint = NULL;

    if (!indexConstraint || (index && index->getLowInt() >= maxIndex + 1)) {
        // Everything past this point in the block is dead, since the exception
        // will always be taken.
        //
        vp->mustTakeException();
        return node;
    }

    vp->addBlockConstraint(indexNode, indexConstraint);

    // After the BNDCHK succeeds, we know the array size must be at least as
    // large as the low end of the index range + 1.
    //
    int32_t minSize = indexConstraint->getLowInt() + 1;
    int32_t maxSize = static_cast<int32_t>(TR::Compiler->om.maxArraySizeInElements(elementSize, vp->comp()));
    TR::VPConstraint *sizeAfter = TR::VPIntRange::create(vp, minSize, maxSize);
    if (sizeBefore)
        sizeAfter = sizeBefore->intersect(sizeAfter, vp);

    vp->addBlockConstraint(sizeNode, sizeAfter);

    // Propagate the array size down to the underlying array object
    //
    if (sizeNode->getOpCode().isArrayLength()) {
        TR::Node *objectRef = sizeNode->getFirstChild();
        vp->addBlockConstraint(objectRef, TR::VPArrayInfo::create(vp, minSize, maxSize, 0));
    }
    return node;
}

TR::Node *constrainBndChkWithSpineChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    // There are two parts to the bounds check, the array size and the
    // index value to be checked.
    //
    int32_t arrayChildNum = 1;
    int32_t boundChildNum = 2;
    int32_t indexChildNum = 3;

    TR::Node *sizeNode = node->getChild(boundChildNum);
    TR::Node *indexNode = node->getChild(indexChildNum);

    bool isGlobal;
    TR::VPConstraint *sizeConstraint = vp->getConstraint(sizeNode, isGlobal);
    TR::VPConstraint *indexConstraint = vp->getConstraint(indexNode, isGlobal);

    bool removeBndChk = false;
    bool removeSpineChk = false;

    // See if the underlying array has bound limits
    //
    TR::Node *objectRef = node->getChild(arrayChildNum);
    TR::VPConstraint *objConstraint = vp->getConstraint(objectRef, isGlobal);

    if (indexConstraint && objConstraint) {
        TR::VPArrayInfo *arrayInfo = objConstraint->getArrayInfo();
        if (arrayInfo) {
            int32_t lowerBoundLimit = arrayInfo->lowBound();
            if (indexConstraint->getLowInt() >= 0 && indexConstraint->getHighInt() < lowerBoundLimit
                && performTransformation(vp->comp(), "%sRemoving unnecessary bound check from composite node [%p]\n",
                    OPT_DETAILS, node)) {
                removeBndChk = true;
            }
        }
    }

    // -------------------------------------------------------------------------
    // First determine if we can fold the bound or spine check (or both).
    // -------------------------------------------------------------------------

    // If the index has a range [0  -> less than the lower bound of the size]
    //       then remove the bound check.
    //
    if (sizeConstraint && indexConstraint) {
        if (indexConstraint->getLowInt() >= 0 && indexConstraint->getHighInt() < sizeConstraint->getLowInt()
            && performTransformation(vp->comp(), "%sRemoving unnecessary bound check from composite node [%p]\n",
                OPT_DETAILS, node)) {
            removeBndChk = true;
        }
    }

    if (sizeConstraint && !vp->comp()->generateArraylets()) {
        // If we can determine the element size then we can possibly eliminate the spine check.
        //
        int32_t elementSize = 0;

        if (sizeNode->getOpCode().isArrayLength()) {
            elementSize = sizeNode->getArrayStride();
        }

        if (!elementSize) {
            // See if the base array has bound limits
            //
            TR::Node *objectRef = node->getSecondChild();
            bool isGlobal;
            TR::VPConstraint *constraint = vp->getConstraint(objectRef, isGlobal);

            if (constraint) {
                TR::VPArrayInfo *arrayInfo = constraint->getArrayInfo();
                if (arrayInfo) {
                    elementSize = arrayInfo->elementSize();
                }
            }

            // If the element size is still not known, try to get it from the node or
            // from the signature, and then propagate it back down to the object ref.
            //
            if (!elementSize && constraint) {
                int32_t len;
                const char *sig = constraint->getClassSignature(len);
                if (sig)
                    elementSize = arrayElementSize(sig, len, objectRef, vp);
            }
        }

        if (elementSize > 0 && !TR::Compiler->om.isDiscontiguousArray(sizeConstraint->getHighInt(), elementSize)
            && (sizeConstraint->getLowInt() > 0)
            && performTransformation(vp->comp(),
                "%sRemoving unnecessary spine check from composite node [%p] : min arraylength = %d, max "
                "arraylength=%d, element size=%d\n",
                OPT_DETAILS, node, sizeConstraint->getLowInt(), sizeConstraint->getHighInt(), elementSize)) {
            removeSpineChk = true;
        }
    }

    bool globalFlag;
    TR::VPConstraint *relativeConstraint = vp->getConstraint(indexNode, globalFlag, sizeNode);
    if (!removeBndChk && relativeConstraint && relativeConstraint->mustBeLessThan()
        && performTransformation(vp->comp(), "%sRemoving redundant bound check from node (subsumed) [%p]\n",
            OPT_DETAILS, node)) {
        removeBndChk = true;
    }

    // -------------------------------------------------------------------------
    // Now do the work to remove the bound or spine check.
    // -------------------------------------------------------------------------

    if (removeBndChk && removeSpineChk) {
        // Change this node to a treetop containing the index.
        //
        TR::Node::recreate(node, TR::treetop);

        TR::Node *arrayElement = node->getChild(0);
        TR::Node *baseArray = node->getChild(1);
        vp->removeNode(baseArray);
        vp->removeNode(sizeNode);
        node->setChild(0, indexNode);
        node->setChild(1, NULL);
        node->setChild(2, NULL);
        node->setChild(3, NULL);
        node->setNumChildren(1);
        vp->setChecksRemoved();

        TR::Node *treeTopNode;
        if (arrayElement->getOpCode().isStore()) {
            treeTopNode = arrayElement;
        } else {
            treeTopNode = TR::Node::create(TR::treetop, 1, arrayElement);
        }

        arrayElement->decReferenceCount();

        TR::TreeTop *newTree = TR::TreeTop::create(vp->comp(), treeTopNode);
        TR::TreeTop *prevTree = vp->_curTree;
        newTree->join(prevTree->getNextTreeTop());
        prevTree->join(newTree);

        return node;
    } else if (removeBndChk) {
        // Convert the node to a spine check.
        //
        TR::Node::recreate(node, TR::SpineCHK);

        vp->removeNode(sizeNode);
        node->setChild(2, indexNode);
        node->setChild(3, NULL);
        node->setNumChildren(3);
        vp->setChecksRemoved();
        return node;
    } else if (removeSpineChk) {
        // Convert the node to a bound check.
        //
        TR::Node::recreate(node, TR::BNDCHK);
        TR::Node *arrayElement = node->getChild(0);
        TR::Node *baseArray = node->getChild(1);
        vp->removeNode(baseArray);
        node->setChild(0, sizeNode);
        node->setChild(1, indexNode);
        node->setChild(2, NULL);
        node->setChild(3, NULL);
        node->setNumChildren(2);

        TR::Node *treeTopNode;
        if (arrayElement->getOpCode().isStore()) {
            treeTopNode = arrayElement;
        } else {
            treeTopNode = TR::Node::create(TR::treetop, 1, arrayElement);
        }

        arrayElement->decReferenceCount();

        TR::TreeTop *newTree = TR::TreeTop::create(vp->comp(), treeTopNode);
        TR::TreeTop *prevTree = vp->_curTree;
        newTree->join(prevTree->getNextTreeTop());
        prevTree->join(newTree);

        return constrainBndChk(vp, node);
    }

    if (vp->_enableVersionBlocks && !vp->_disableVersionBlockForThisBlock && vp->lastTimeThrough())
        vp->_bndChecks->add(node);

    vp->createExceptionEdgeConstraints(TR::Block::CanCatchBoundCheck, NULL, node);

    if ((sizeNode == indexNode) && !((sizeNode->getOpCodeValue() == TR::iconst) && (sizeNode->getInt() == 0))) {
        vp->mustTakeException();
        return node;
    }

    // -------------------------------------------------------------------------
    // We can now make some assertions about the index value:
    //    1) its low bound must be >= 0
    //    2) its high bound must be (maximum array size-1)
    // -------------------------------------------------------------------------

    int32_t elementSize = 1;
    if (sizeNode->getOpCode().isArrayLength()) {
        elementSize = sizeNode->getArrayStride();
    }

    int32_t low = 0;
    int32_t high = (int32_t)TR::Compiler->om.maxArraySizeInElements(elementSize, vp->comp()) - 1;

    // Calculate the new constraint for the index value.  If it is null, we
    // will definitely fail the bndchk
    //
    TR::VPConstraint *constraint = NULL;
    bool foundArrayInfo = false;

    // See if the underlying array has bound limits
    //
    if (objConstraint) {
        TR::VPArrayInfo *arrayInfo = objConstraint->getArrayInfo();
        if (arrayInfo) {
            int32_t upperBoundLimit = arrayInfo->highBound();
            constraint = TR::VPIntRange::create(vp, 0, upperBoundLimit - 1);
            foundArrayInfo = true;
        }
    }

    if (!constraint)
        constraint = TR::VPIntRange::create(vp, low, high);

    if (indexConstraint && constraint)
        constraint = indexConstraint->intersect(constraint, vp);

    if (!constraint || (indexConstraint && indexConstraint->getLowInt() >= high + 1)) {
        // Everything past this point in the block is dead, since the exception
        // will always be taken.
        //
        vp->mustTakeException();
        return node;
    }

    vp->addBlockConstraint(indexNode, constraint);

    // Propagate the array size down to the underlying array object
    //
    if (sizeNode->getOpCode().isArrayLength()) {
        // Get the low bound from the new index constraint. If n is the length
        // and i is the index, then we know that n > i >= low, so n >= low + 1.
        // Leave high unchanged because the high bound from indexConstraint is
        // uninformative.
        low = constraint->getLowInt();

        TR::Node *objectRef = sizeNode->getFirstChild();
        vp->addBlockConstraint(objectRef, TR::VPArrayInfo::create(vp, low + 1, high + 1, 0));
    }

    return node;
}

TR::Node *constrainArrayCopyBndChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    TR::Node *lhsNode = node->getFirstChild();
    TR::Node *rhsNode = node->getSecondChild();

    bool isGlobal;
    TR::VPConstraint *lhs = vp->getConstraint(lhsNode, isGlobal);
    TR::VPConstraint *rhs = vp->getConstraint(rhsNode, isGlobal);

    // If the lhs is guaranteed to be >= the rhs, remove the bound check.
    //
    if ((vp->getValueNumber(lhsNode) == vp->getValueNumber(rhsNode))
        || (lhs && rhs && lhs->getLowInt() >= rhs->getHighInt())) {
        if (performTransformation(vp->comp(), "%sRemoving redundant arraycopy bound check node [%p]\n", OPT_DETAILS,
                node)) {
            vp->removeNode(node);
            vp->setChecksRemoved();
            return NULL;
        }
    }

    // TODO - create a constraint along the exception edge if possible
    //
    vp->createExceptionEdgeConstraints(TR::Block::CanCatchBoundCheck, NULL, node);

    // We can now make some assertions about the children
    //    1) Both of them must be >= 0
    //    2) Both of them must be  < maximum array size
    //    3) The lhs low bound >= the rhs low bound
    //    3) The rhs high bound <= the lhs high bound
    //
    int32_t elementSize = 1;
    bool isArrayLengthCheck = false;
    if (lhsNode->getOpCode().isArrayLength()) {
        elementSize = lhsNode->getArrayStride();
        isArrayLengthCheck = true;
    }

    int32_t low = 0;
    int32_t high = (int32_t)TR::Compiler->om.maxArraySizeInElements(elementSize, vp->comp());

    if (lhs && lhs->getHighInt() < high)
        high = lhs->getHighInt();

    if (rhs && rhs->getLowInt() > low)
        low = rhs->getLowInt();

    // Calculate the new constraints for the children.
    //
    TR::VPConstraint *constraint = NULL;
    TR::VPConstraint *lhsConstraint = NULL;
    TR::VPConstraint *rhsConstraint = NULL;
    if (low <= high) {
        constraint = TR::VPIntRange::create(vp, low, high);
        if (lhs)
            lhsConstraint = lhs->intersect(constraint, vp);
        else
            lhsConstraint = constraint;
        if (rhs)
            rhsConstraint = rhs->intersect(constraint, vp);
        else
            rhsConstraint = constraint;
    } else
        lhsConstraint = NULL;

    if (!(lhsConstraint && rhsConstraint)) {
        // Everything past this point in the block is dead, since the exception
        // will always be taken.
        //
        vp->mustTakeException();
        return node;
    }

    vp->addBlockConstraint(lhsNode, lhsConstraint);
    vp->addBlockConstraint(rhsNode, rhsConstraint);

    // If this is an arraylength check, the arraylength range can be propagated
    // down to the underlying array
    //
    if (isArrayLengthCheck) {
        TR::Node *objectRef = lhsNode->getFirstChild();
        vp->addBlockConstraint(objectRef,
            TR::VPArrayInfo::create(vp, lhsConstraint->getLowInt(), lhsConstraint->getHighInt(), 0));
    }
    return node;
}

TR::Node *constrainArrayStoreChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    TR::Node *child = node->getFirstChild();
    TR::Node *objectRef = child->getSecondChild();
    TR::Node *arrayRef = child->getChild(2);
    bool mustFail = false;
    bool valueIsFromSameArray = false;

    if (objectRef->getOpCode().isLoadVar() && objectRef->getOpCode().isIndirect()
        && objectRef->getFirstChild()->isInternalPointer()) {
        TR::Node *base = objectRef->getFirstChild()->getFirstChild();
        if (base->getOpCode().hasSymbolReference() && base->getSymbol()->isArrayletShadowSymbol()) {
            if (base->getFirstChild()->getOpCode().isArrayRef())
                base = base->getFirstChild()->getFirstChild();
        }

        valueIsFromSameArray = (vp->getValueNumber(base) == vp->getValueNumber(arrayRef));
    }

    TR_OpaqueClassBlock *storeClassForCheck = NULL;
    TR_OpaqueClassBlock *componentClassForCheck = NULL;
    bool isStoreCheckNeeded = !valueIsFromSameArray
        && vp->isArrayStoreCheckNeeded(arrayRef, objectRef, mustFail, storeClassForCheck, componentClassForCheck);

    // Remove the check if we can
    //
    if (!isStoreCheckNeeded) {
        // Change the write barrier into astorei
        //
        canRemoveWrtBar(vp, child);

        if (performTransformation(vp->comp(), "%sRemoving redundant arraystore check node [%p]\n", OPT_DETAILS, node)) {
            // Child is the astorei. Just change this node to a treetop
            //
            TR::Node::recreate(node, TR::treetop);
            // DemandLiteralPool can add extra aload to arraystore check node, but treetop can only have one child
            if (vp->comp()->cg()->supportsOnDemandLiteralPool() && node->getNumChildren() > 1) {
                vp->removeNode(node->getChild(1));
                node->setNumChildren(1);
            }

            vp->setChecksRemoved();
            return node;
        }
    }

    if (storeClassForCheck != NULL) {
        if (vp->trace()) {
            traceMsg(vp->comp(), "Setting arrayStoreClass on ArrayStoreChk node [%p] to [%p]\n", node,
                storeClassForCheck);
        }
        node->setArrayStoreClassInNode(storeClassForCheck);
    } else if (componentClassForCheck != NULL) {
        if (vp->trace()) {
            traceMsg(vp->comp(), "Setting arrayComponentClass on ArrayStoreChk node [%p] to [%p]\n", node,
                componentClassForCheck);
        }
        node->setArrayComponentClassInNode(componentClassForCheck);
    }

    vp->createExceptionEdgeConstraints(TR::Block::CanCatchArrayStoreCheck, NULL, node);

    // If the check must fail, remove the rest of the block
    //
    if (mustFail) {
        vp->mustTakeException();
    }
    return node;
}

TR::Node *constrainArrayChk(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    TR::Node *srcNode = node->getFirstChild();
    TR::Node *dstNode = node->getSecondChild();

    bool isGlobal;
    TR::VPConstraint *src = vp->getConstraint(srcNode, isGlobal);
    TR::VPConstraint *dst = vp->getConstraint(dstNode, isGlobal);
    TR::VPClassType *srcType = src ? src->getClassType() : NULL;
    TR::VPClassType *dstType = dst ? dst->getClassType() : NULL;
    bool removeThisNode = false;

    // If either array is null we can remove this check since this node will
    // never be reached.
    //
    if (src && src->isNullObject())
        removeThisNode = true;
    else if (src && src->isNullObject())
        removeThisNode = true;

    // If the arrays are the same object we can remove this check
    //
    else if (srcNode == dstNode || vp->getValueNumber(srcNode) == vp->getValueNumber(dstNode)) {
        removeThisNode = true;
    }

    // If the arrays are of the same primitive type we can remove this check
    //
    else if (srcType && srcType == dstType && srcType->isPrimitiveArray(vp->comp())) {
        removeThisNode = true;
    }

    if (removeThisNode
        && performTransformation(vp->comp(), "%sRemoving redundant array check node [%p]\n", OPT_DETAILS, node)) {
        // Re-anchor the children and mark this tree as removable
        //
        vp->removeNode(node);
        return NULL;
    }

    if (srcType) {
        if (srcType->isPrimitiveArray(vp->comp()))
            node->setArrayChkPrimitiveArray1(true);
        else if (srcType->isReferenceArray(vp->comp()))
            node->setArrayChkReferenceArray1(true);
    }
    if (dstType) {
        if (dstType->isPrimitiveArray(vp->comp()))
            node->setArrayChkPrimitiveArray2(true);
        else if (dstType->isReferenceArray(vp->comp()))
            node->setArrayChkReferenceArray2(true);
    }

    vp->createExceptionEdgeConstraints(TR::Block::CanCatchArrayStoreCheck, NULL, node);
    return node;
}

TR::Node *constrainArraycopy(OMR::ValuePropagation *vp, TR::Node *node)
{
    constrainChildren(vp, node);

    TR::Node *srcObjNode = NULL;
    TR::Node *srcNode = NULL;
    TR::Node *dstObjNode = NULL;
    TR::Node *dstNode = NULL;
    TR::Node *lenNode = NULL;

    if (node->getNumChildren() == 5) {
        srcObjNode = node->getFirstChild();
        dstObjNode = node->getSecondChild();
        srcNode = node->getChild(2);
        dstNode = node->getChild(3);
        lenNode = node->getChild(4);
    } else {
        srcNode = node->getFirstChild();
        dstNode = node->getSecondChild();
        lenNode = node->getChild(2);
    }

    bool isGlobal;
    TR::VPConstraint *len = vp->getConstraint(lenNode, isGlobal);

    // If the length to be copied is zero, remove the arraycopy itself
    //
    if (len && node->getNumChildren() == 3 && len->asIntConst() && len->getLowInt() == 0
        && performTransformation(vp->comp(), "%sRemoving arraycopy node [%p]\n", OPT_DETAILS, node)) {
        vp->removeArrayCopyNode(vp->_curTree);
        vp->removeNode(node);
        vp->_curTree->setNode(NULL);
        vp->invalidateUseDefInfo();
        vp->invalidateValueNumberInfo();
        return node;
    }

    // If this is the 5-children version of arraycopy, see if it can be changed
    // into the 3-children version.
    //
    if (node->getNumChildren() == 5) {
        TR::VPConstraint *src = vp->getConstraint(srcObjNode, isGlobal);
        TR::VPConstraint *dst = vp->getConstraint(dstObjNode, isGlobal);
        TR::VPClassType *srcType = src ? src->getClassType() : NULL;
        TR::VPClassType *dstType = dst ? dst->getClassType() : NULL;
        TR::DataType elementType = TR::NoType;
        if (srcType && srcType->isPrimitiveArray(vp->comp()))
            elementType = srcType->getPrimitiveArrayDataType();
        else if (dstType && dstType->isPrimitiveArray(vp->comp()))
            elementType = dstType->getPrimitiveArrayDataType();
        if (elementType != TR::NoType
            && performTransformation(vp->comp(), "%sTransforming arraycopy node [%p]\n", OPT_DETAILS, node)) {
            node->setChild(0, srcNode);
            node->setChild(1, dstNode);
            node->setChild(2, lenNode);
            node->setChild(3, NULL);
            node->setChild(4, NULL);
            srcObjNode->recursivelyDecReferenceCount();
            dstObjNode->recursivelyDecReferenceCount();
            node->setNumChildren(3);
            node->setArrayCopyElementType(elementType);
            vp->invalidateUseDefInfo();
            vp->invalidateValueNumberInfo();
            /// node->setReferenceArrayCopy(false, vp->comp());
        }
    }

    // If this is the 3-child version of arraycopy, see if the copy can be done
    // as a scalar copy
    //

    if (node->getNumChildren() == 3) {
        ListIterator<OMR::ValuePropagation::TR_TreeTopNodePair> treesIt1(&vp->_scalarizedArrayCopies);
        bool alreadyAdded = false;
        OMR::ValuePropagation::TR_TreeTopNodePair *scalarizedArrayCopy;
        for (scalarizedArrayCopy = treesIt1.getFirst(); scalarizedArrayCopy; scalarizedArrayCopy = treesIt1.getNext()) {
            if (scalarizedArrayCopy->_node == node) {
                alreadyAdded = true;
                break;
            }
        }

        if (!alreadyAdded)
            vp->_scalarizedArrayCopies.add(
                new (vp->comp()->trStackMemory()) OMR::ValuePropagation::TR_TreeTopNodePair(vp->_curTree, node));
    } else {
        vp->createExceptionEdgeConstraints(TR::Block::CanCatchArrayStoreCheck, NULL, node);
    }

    return node;
}

TR::Node *constrainIntegralToBCD(OMR::ValuePropagation *vp, TR::Node *node)
{
#ifdef J9_PROJECT_SPECIFIC
    constrainChildren(vp, node);

    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node->getFirstChild(), isGlobal);

    int32_t prec = TR_MAX_DECIMAL_PRECISION;

    if (constraint)
        prec = constraint->getPrecision();
    else {
        if (node->getFirstChild()->getType().isInt16())
            prec = std::min(prec, getPrecisionFromValue(TR::getMaxSigned<TR::Int16>()));
        else if (node->getFirstChild()->getType().isInt32())
            prec = std::min(prec, getPrecisionFromValue(TR::getMaxSigned<TR::Int32>()));
        else if (node->getFirstChild()->getType().isInt64())
            prec = std::min(prec, getPrecisionFromValue(TR::getMaxSigned<TR::Int64>()));
    }

    if (prec > node->getSourcePrecision())
        return node;

    if (performTransformation(vp->comp(), "%sSetting source precision on node %s [0x%x] to %d\n", OPT_DETAILS,
            node->getOpCode().getName(), node, prec))
        node->setSourcePrecision(prec);

#endif
    return node;
}

TR::Node *constrainBCDToIntegral(OMR::ValuePropagation *vp, TR::Node *node)
{
#ifdef J9_PROJECT_SPECIFIC
    constrainChildren(vp, node);

    bool isGlobal;
    TR::VPConstraint *constraint = vp->getConstraint(node, isGlobal);

    int32_t p = node->getFirstChild()->getDecimalPrecision();
    int64_t lo, hi;

    if (node->getType().isInt64()) {
        // If a node has precision < the max precision (for its data type) and has the isNonNegative (x >= 0) flag set,
        // we know that it can't possibly be represented as a negative number, so the min value for the constraint must
        // be at least 0
        if (p < node->getMaxIntegerPrecision() && node->isNonNegative())
            constrainRangeByPrecision(TR::getMinSigned<TR::Int64>(), TR::getMaxSigned<TR::Int64>(), p, lo, hi, true);
        else
            constrainRangeByPrecision(TR::getMinSigned<TR::Int64>(), TR::getMaxSigned<TR::Int64>(), p, lo, hi);
    } else {
        if (p < node->getMaxIntegerPrecision() && node->isNonNegative())
            constrainRangeByPrecision(TR::getMinSigned<TR::Int32>(), TR::getMaxSigned<TR::Int32>(), p, lo, hi, true);
        else
            constrainRangeByPrecision(TR::getMinSigned<TR::Int32>(), TR::getMaxSigned<TR::Int32>(), p, lo, hi);
    }

    if (node->getType().isInt64())
        constraint = TR::VPLongRange::create(vp, lo, hi);
    else
        constraint = TR::VPIntRange::create(vp, lo, hi);

    if (constraint) {
        vp->addBlockOrGlobalConstraint(node, constraint, isGlobal);
        checkForNonNegativeAndOverflowProperties(vp, node, constraint);
    }

#endif
    return node;
}
