/*******************************************************************************
 * Copyright IBM Corp. and others 2018
 *
 * 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
 *******************************************************************************/

#ifndef ARM64INSTRUCTION_INCL
#define ARM64INSTRUCTION_INCL

#include <stddef.h>
#include <stdint.h>

#include "codegen/ARM64ConditionCode.hpp"
#include "codegen/ARM64ShiftCode.hpp"
#include "codegen/CodeGenerator.hpp"
#include "codegen/Instruction.hpp"
#include "codegen/MemoryReference.hpp"
#include "codegen/RegisterDependency.hpp"
#include "codegen/UnresolvedDataSnippet.hpp"
#include "il/LabelSymbol.hpp"
#include "infra/Assert.hpp"

class TR_VirtualGuardSite;

namespace TR {
class SymbolReference;
}

#define ARM64_INSTRUCTION_LENGTH 4

/*
 * Prefetch type used in PRFM instructions
 */
enum class ARM64PrefetchType {
    LOAD = 0,
    INSTRUCTION = 1,
    STORE = 2
};

/*
 * Prefetch target used in PRFM instructions
 */
enum class ARM64PrefetchTarget {
    L1 = 0,
    L2 = 1,
    L3 = 2
};

/*
 * Prefetch policy used in PRFM instructions
 */
enum class ARM64PrefetchPolicy {
    KEEP = 0,
    STRM = 1
};

/*
 * @brief returns Prefetch operation from specified type, target, and policy.
 * @param[in] type : prefetch type
 * @param[in] target : prefetch target
 * @param[in] policy : prefetch policy
 * @return prefetch operation
 */
inline uint32_t toPrefetchOp(ARM64PrefetchType type, ARM64PrefetchTarget target, ARM64PrefetchPolicy policy)
{
    return (static_cast<uint32_t>(type) << 3) | (static_cast<uint32_t>(target) << 1) | static_cast<uint32_t>(policy);
}

/*
 * @brief Answers if the signed integer value can be placed in 7-bit field
 * @param[in] intValue : signed integer value
 * @return true if the value can be placed in 7-bit field, false otherwise
 */
inline bool constantIsImm7(int32_t intValue) { return (-64 <= intValue && intValue < 64); }

/*
 * @brief Answers if the signed integer value can be placed in 9-bit field
 * @param[in] intValue : signed integer value
 * @return true if the value can be placed in 9-bit field, false otherwise
 */
inline bool constantIsImm9(int64_t intValue) { return (-256 <= intValue && intValue < 256); }

/*
 * @brief Answers if the unsigned integer value can be encoded in a 4-bit field
 * @param[in] intValue : unsigned integer value
 * @return true if the value can be encoded in a 4-bit field, false otherwise
 */
inline bool constantIsUnsignedImm4(uint64_t intValue)
{
    return (intValue < (1 << 4)); // 16
}

/*
 * @brief Answers if the unsigned integer value can be encoded in a 5-bit field
 * @param[in] intValue : unsigned integer value
 * @return true if the value can be encoded in a 5-bit field, false otherwise
 */
inline bool constantIsUnsignedImm5(uint64_t intValue)
{
    return (intValue < (1 << 5)); // 32
}

/*
 * @brief Answers if the unsigned integer value can be encoded in a 12-bit field
 * @param[in] intValue : unsigned integer value
 * @return true if the value can be encoded in a 12-bit field, false otherwise
 */
inline bool constantIsUnsignedImm12(uint64_t intValue)
{
    return (intValue < (1 << 12)); // 4096
}

/*
 * @brief Answers if the unsigned integer value can be encoded in a 12-bit field with 12 bits shift
 * @param[in] intValue : unsigned integer value
 * @return true if the value can be encoded in a 12-bit field with 12 bits shift, false otherwise
 */
inline bool constantIsUnsignedImm12Shifted(uint64_t intValue)
{
    return ((intValue & (~(static_cast<uint64_t>(0xfff000)))) == 0);
}

/*
 * @brief Answers if the signed integer value can be placed in a 16-bit field
 * @param[in] intValue : signed integer value
 * @return true if the value can be placed in 16-bit field, false otherwise
 */
inline bool constantIsSignedImm16(intptr_t intValue) { return (-0x8000 <= intValue && intValue < 0x8000); }

/*
 * @brief Answers if the unsigned integer value can be encoded in a 16-bit field
 * @param[in] intValue : unsigned integer value
 * @return true if the value can be encoded in a 16-bit field, false otherwise
 */
inline bool constantIsUnsignedImm16(uint64_t intValue)
{
    return (intValue < (1 << 16)); // 65536
}

/*
 * @brief Answers if the signed integer value can be placed in 21-bit field
 * @param[in] intValue : signed integer value
 * @return true if the value can be placed in 21-bit field, false otherwise
 */
inline bool constantIsSignedImm21(intptr_t intValue) { return (-0x100000 <= intValue && intValue < 0x100000); }

/*
 * @brief Answers if the signed integer value can be placed in 28-bit field
 * @param[in] intValue : signed integer value
 * @return true if the value can be placed in 28-bit field, false otherwise
 */
inline bool constantIsSignedImm28(intptr_t intValue) { return (-0x8000000 <= intValue && intValue < 0x8000000); }

namespace TR {

class ARM64ImmInstruction : public TR::Instruction {
    uint32_t _sourceImmediate;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uint32_t imm, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cg)
        , _sourceImmediate(imm)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uint32_t imm,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, precedingInstruction, cg)
        , _sourceImmediate(imm)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsImm; }

    /**
     * @brief Gets source immediate
     * @return source immediate
     */
    uint32_t getSourceImmediate() { return _sourceImmediate; }

    /**
     * @brief Sets source immediate
     * @param[in] si : immediate value
     * @return source immediate
     */
    uint32_t setSourceImmediate(uint32_t si) { return (_sourceImmediate = si); }

    /**
     * @brief Encodes the immediate value into the instruction
     * @param[in] instruction : instruction address
     */
    virtual void insertImmediateField(uint32_t *instruction)
    {
        // Write the 32-bit immediate with no masking or shifting.
        // Be aware this will overwrite the entire 32-bit instruction
        // with the immediate value.
        //
        *instruction = _sourceImmediate;
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64RelocatableImmInstruction : public TR::Instruction {
    uintptr_t _sourceImmediate;
    TR_ExternalRelocationTargetKind _reloKind;
    TR::SymbolReference *_symbolReference;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] relocationKind : relocation kind
     * @param[in] cg : CodeGenerator
     */
    ARM64RelocatableImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uintptr_t imm,
        TR_ExternalRelocationTargetKind relocationKind, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cg)
        , _sourceImmediate(imm)
        , _reloKind(relocationKind)
        , _symbolReference(NULL)
    {
        setNeedsAOTRelocation();
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] relocationKind : relocation kind
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64RelocatableImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uintptr_t imm,
        TR_ExternalRelocationTargetKind relocationKind, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, precedingInstruction, cg)
        , _sourceImmediate(imm)
        , _reloKind(relocationKind)
        , _symbolReference(NULL)
    {
        setNeedsAOTRelocation();
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] relocationKind : relocation kind
     * @param[in] sr : symbol reference
     * @param[in] cg : CodeGenerator
     */
    ARM64RelocatableImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uintptr_t imm,
        TR_ExternalRelocationTargetKind relocationKind, TR::SymbolReference *sr, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cg)
        , _sourceImmediate(imm)
        , _reloKind(relocationKind)
        , _symbolReference(sr)
    {
        setNeedsAOTRelocation();
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] relocationKind : relocation kind
     * @param[in] sr : symbol reference
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64RelocatableImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uintptr_t imm,
        TR_ExternalRelocationTargetKind relocationKind, TR::SymbolReference *sr, TR::Instruction *precedingInstruction,
        TR::CodeGenerator *cg)
        : TR::Instruction(op, node, precedingInstruction, cg)
        , _sourceImmediate(imm)
        , _reloKind(relocationKind)
        , _symbolReference(sr)
    {
        setNeedsAOTRelocation();
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsRelocatableImm; }

    /**
     * @brief Gets source immediate
     * @return source immediate
     */
    uintptr_t getSourceImmediate() { return _sourceImmediate; }

    /**
     * @brief Sets source immediate
     * @param[in] si : immediate value
     * @return source immediate
     */
    uintptr_t setSourceImmediate(intptr_t si) { return (_sourceImmediate = si); }

    /**
     * @brief Encodes the immediate value into the instruction
     * @param[in] instruction : instruction address
     */
    void insertImmediateField(uintptr_t *instruction) { *instruction = _sourceImmediate; }

    /**
     * @brief Sets relocation kind
     * @param[in] reloKind : relocation kind
     * @return relocation kind
     */
    TR_ExternalRelocationTargetKind setReloKind(TR_ExternalRelocationTargetKind reloKind)
    {
        return (_reloKind = reloKind);
    }

    /**
     * @brief Gets relocation kind
     * @return relocation kind
     */
    TR_ExternalRelocationTargetKind getReloKind() { return _reloKind; }

    /**
     * @brief Sets symbol reference
     * @param[in] sr : symbol reference
     * @return symbol reference
     */
    TR::SymbolReference *setSymbolReference(TR::SymbolReference *sr) { return (_symbolReference = sr); }

    /**
     * @brief Gets symbol reference
     * @return symbol reference
     */
    TR::SymbolReference *getSymbolReference() { return _symbolReference; }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

class ARM64ImmSymInstruction : public TR::Instruction {
    uintptr_t _addrImmediate;
    TR::SymbolReference *_symbolReference;
    TR::Snippet *_snippet;

public:
    ARM64ImmSymInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uintptr_t imm,
        TR::RegisterDependencyConditions *cond, TR::SymbolReference *sr, TR::Snippet *s, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, cg)
        , _addrImmediate(imm)
        , _symbolReference(sr)
        , _snippet(s)
    {}

    ARM64ImmSymInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uintptr_t imm,
        TR::RegisterDependencyConditions *cond, TR::SymbolReference *sr, TR::Snippet *s,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, precedingInstruction, cg)
        , _addrImmediate(imm)
        , _symbolReference(sr)
        , _snippet(s)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsImmSym; }

    /**
     * @brief Gets address immediate
     * @return address immediate
     */
    uintptr_t getAddrImmediate() { return _addrImmediate; }

    /**
     * @brief Sets address immediate
     * @param[in] imm : address immediate
     * @return address immediate
     */
    uintptr_t setAddrImmediate(uintptr_t imm) { return (_addrImmediate = imm); }

    /**
     * @brief Gets symbol reference
     * @return symbol reference
     */
    TR::SymbolReference *getSymbolReference() { return _symbolReference; }

    /**
     * @brief Sets symbol reference
     * @param[in] sr : symbol reference
     * @return symbol reference
     */
    TR::SymbolReference *setSymbolReference(TR::SymbolReference *sr) { return (_symbolReference = sr); }

    /**
     * @brief Gets call snippet
     * @return call snippet
     */
    TR::Snippet *getCallSnippet() { return _snippet; }

    /**
     * @brief Sets call snippet
     * @param[in] s : call snippet
     * @return call snippet
     */
    TR::Snippet *setCallSnippet(TR::Snippet *s) { return (_snippet = s); }

    virtual TR::Snippet *getSnippetForGC() { return _snippet; }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     * @param[in] distance : branch distance
     */
    void insertImmediateField(uint32_t *instruction, int32_t distance)
    {
        TR_ASSERT((distance & 0x3) == 0, "branch distance is not aligned");
        *instruction |= ((distance >> 2) & 0x3ffffff); // imm26
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64LabelInstruction : public TR::Instruction {
    TR::LabelSymbol *_symbol;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sym : label symbol
     * @param[in] cg : CodeGenerator
     */
    ARM64LabelInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::LabelSymbol *sym, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cg)
        , _symbol(sym)
    {
        if (sym != NULL && op == TR::InstOpCode::label)
            sym->setInstruction(this);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sym : label symbol
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64LabelInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::LabelSymbol *sym,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, precedingInstruction, cg)
        , _symbol(sym)
    {
        if (sym != NULL && op == TR::InstOpCode::label)
            sym->setInstruction(this);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sym : label symbol
     * @param[in] cond : register dependency condition
     * @param[in] cg : CodeGenerator
     */
    ARM64LabelInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::LabelSymbol *sym,
        TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, cg)
        , _symbol(sym)
    {
        if (sym != NULL && op == TR::InstOpCode::label)
            sym->setInstruction(this);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sym : label symbol
     * @param[in] cond : register dependency condition
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64LabelInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::LabelSymbol *sym,
        TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, precedingInstruction, cg)
        , _symbol(sym)
    {
        if (sym != NULL && op == TR::InstOpCode::label)
            sym->setInstruction(this);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsLabel; }

    /**
     * @brief Gets label symbol
     * @return label symbol
     */
    TR::LabelSymbol *getLabelSymbol() { return _symbol; }

    /**
     * @brief Sets label symbol
     * @param[in] sym : label symbol
     * @return label symbol
     */
    TR::LabelSymbol *setLabelSymbol(TR::LabelSymbol *sym) { return (_symbol = sym); }

    virtual TR::Snippet *getSnippetForGC() { return getLabelSymbol()->getSnippet(); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     * @param[in] distance : branch distance
     */
    void insertImmediateField(uint32_t *instruction, int32_t distance)
    {
        TR_ASSERT((distance & 0x3) == 0, "branch distance is not aligned");
        *instruction |= ((distance >> 2) & 0x3ffffff); // imm26
    }

    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);

protected:
    /**
     * @brief Assigns registers for OutOfLineCodeSection
     *
     * @param[in] kindToBeAssigned : register kind
     */
    void assignRegistersForOutOfLineCodeSection(TR_RegisterKinds kindToBeAssigned);
};

class ARM64ConditionalBranchInstruction : public ARM64LabelInstruction {
    int32_t _estimatedBinaryLocation;
    TR::ARM64ConditionCode _cc;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sym : label symbol
     * @param[in] cc : branch condition code
     * @param[in] cg : CodeGenerator
     */
    ARM64ConditionalBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::LabelSymbol *sym,
        TR::ARM64ConditionCode cc, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cg)
        , _cc(cc)
        , _estimatedBinaryLocation(0)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sym : label symbol
     * @param[in] cc : branch condition code
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64ConditionalBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::LabelSymbol *sym,
        TR::ARM64ConditionCode cc, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, precedingInstruction, cg)
        , _cc(cc)
        , _estimatedBinaryLocation(0)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sym : label symbol
     * @param[in] cc : branch condition code
     * @param[in] cond : register dependency condition
     * @param[in] cg : CodeGenerator
     */
    ARM64ConditionalBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::LabelSymbol *sym,
        TR::ARM64ConditionCode cc, TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cond, cg)
        , _cc(cc)
        , _estimatedBinaryLocation(0)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sym : label symbol
     * @param[in] cc : branch condition code
     * @param[in] cond : register dependency condition
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64ConditionalBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::LabelSymbol *sym,
        TR::ARM64ConditionCode cc, TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction,
        TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cond, precedingInstruction, cg)
        , _cc(cc)
        , _estimatedBinaryLocation(0)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsConditionalBranch; }

    /**
     * @brief Gets estimated binary location
     * @return estimated binary location
     */
    int32_t getEstimatedBinaryLocation() { return _estimatedBinaryLocation; }

    /**
     * @brief Sets estimated binary location
     * @param[in] l : estimated binary location
     * @return estimated binary location
     */
    int32_t setEstimatedBinaryLocation(int32_t l) { return (_estimatedBinaryLocation = l); }

    /**
     * @brief Gets condition code
     * @return condition code
     */
    TR::ARM64ConditionCode getConditionCode() { return _cc; }

    /**
     * @brief Sets condition code
     * @param[in] cc : condition code
     * @return condition code
     */
    TR::ARM64ConditionCode setConditionCode(TR::ARM64ConditionCode cc) { return (_cc = cc); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     * @param[in] distance : branch distance
     */
    void insertImmediateField(uint32_t *instruction, int32_t distance)
    {
        TR_ASSERT((distance & 0x3) == 0, "branch distance is not aligned");
        *instruction |= ((distance >> 2) & 0x7ffff) << 5; // imm19
    }

    /**
     * @brief Sets condition code in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertConditionCodeField(uint32_t *instruction) { *instruction |= (_cc & 0xf); }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

class ARM64CompareBranchInstruction : public ARM64LabelInstruction {
    int32_t _estimatedBinaryLocation;
    TR::Register *_source1Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] sym : label symbol
     * @param[in] cg : CodeGenerator
     */
    ARM64CompareBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, TR::LabelSymbol *sym,
        TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cg)
        , _source1Register(sreg)
        , _estimatedBinaryLocation(0)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] sym : label symbol
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64CompareBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, TR::LabelSymbol *sym,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, precedingInstruction, cg)
        , _source1Register(sreg)
        , _estimatedBinaryLocation(0)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] sym : label symbol
     * @param[in] cond : register dependency condition
     * @param[in] cg : CodeGenerator
     */
    ARM64CompareBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, TR::LabelSymbol *sym,
        TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cond, cg)
        , _source1Register(sreg)
        , _estimatedBinaryLocation(0)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] sym : label symbol
     * @param[in] cond : register dependency condition
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64CompareBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, TR::LabelSymbol *sym,
        TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cond, precedingInstruction, cg)
        , _source1Register(sreg)
        , _estimatedBinaryLocation(0)
    {
        useRegister(sreg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsCompareBranch; }

    /**
     * @brief Gets estimated binary location
     * @return estimated binary location
     */
    int32_t getEstimatedBinaryLocation() { return _estimatedBinaryLocation; }

    /**
     * @brief Sets estimated binary location
     * @param[in] l : estimated binary location
     * @return estimated binary location
     */
    int32_t setEstimatedBinaryLocation(int32_t l) { return (_estimatedBinaryLocation = l); }

    /**
     * @brief Gets source register
     * @return source register
     */
    TR::Register *getSource1Register() { return _source1Register; }

    /**
     * @brief Sets source register
     * @param[in] sr : source register
     * @return source register
     */
    TR::Register *setSource1Register(TR::Register *sr) { return (_source1Register = sr); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     * @param[in] distance : branch distance
     */
    void insertImmediateField(uint32_t *instruction, int32_t distance)
    {
        TR_ASSERT((distance & 0x3) == 0, "branch distance is not aligned");
        *instruction |= ((distance >> 2) & 0x7ffff) << 5;
    }

    /**
     * @brief Sets source register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource1Register(uint32_t *instruction)
    {
        TR::RealRegister *source1 = toRealRegister(_source1Register);
        source1->setRegisterFieldRD(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64TestBitBranchInstruction : public ARM64LabelInstruction {
    int32_t _estimatedBinaryLocation;
    TR::Register *_source1Register;
    uint32_t _bitpos;

public:
    /*
     * @brief Constructor
     * @param[in] op     : instruction opcode
     * @param[in] node   : node
     * @param[in] sreg   : source register
     * @param[in] bitpos : bit position to test
     * @param[in] sym    : label symbol
     * @param[in] cg     : CodeGenerator
     */
    ARM64TestBitBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, uint32_t bitpos,
        TR::LabelSymbol *sym, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cg)
        , _source1Register(sreg)
        , _estimatedBinaryLocation(0)
        , _bitpos(bitpos)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op                   : instruction opcode
     * @param[in] node                 : node
     * @param[in] sreg                 : source register
     * @param[in] bitpos               : bit position to test
     * @param[in] sym                  : label symbol
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg                   : CodeGenerator
     */
    ARM64TestBitBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, uint32_t bitpos,
        TR::LabelSymbol *sym, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, precedingInstruction, cg)
        , _source1Register(sreg)
        , _estimatedBinaryLocation(0)
        , _bitpos(bitpos)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op     : instruction opcode
     * @param[in] node   : node
     * @param[in] sreg   : source register
     * @param[in] bitpos : bit position to test
     * @param[in] sym    : label symbol
     * @param[in] cond   : register dependency condition
     * @param[in] cg     : CodeGenerator
     */
    ARM64TestBitBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, uint32_t bitpos,
        TR::LabelSymbol *sym, TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cond, cg)
        , _source1Register(sreg)
        , _estimatedBinaryLocation(0)
        , _bitpos(bitpos)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op                   : instruction opcode
     * @param[in] node                 : node
     * @param[in] sreg                 : source register
     * @param[in] bitpos               : bit position to test
     * @param[in] sym                  : label symbol
     * @param[in] cond                s : register dependency condition
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg                   : CodeGenerator
     */
    ARM64TestBitBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, uint32_t bitpos,
        TR::LabelSymbol *sym, TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction,
        TR::CodeGenerator *cg)
        : ARM64LabelInstruction(op, node, sym, cond, precedingInstruction, cg)
        , _source1Register(sreg)
        , _estimatedBinaryLocation(0)
        , _bitpos(bitpos)
    {
        useRegister(sreg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTestBitBranch; }

    /**
     * @brief Gets bit position
     * @return bit position
     */
    uint32_t getBitPos() { return _bitpos; }

    /**
     * @brief Sets bit position
     * @param[in] bitpos : bit position
     * @return bit position
     */
    uint32_t setBitPos(uint32_t bitpos) { return (_bitpos = bitpos); }

    /**
     * @brief Gets estimated binary location
     * @return estimated binary location
     */
    int32_t getEstimatedBinaryLocation() { return _estimatedBinaryLocation; }

    /**
     * @brief Sets estimated binary location
     * @param[in] l : estimated binary location
     * @return estimated binary location
     */
    int32_t setEstimatedBinaryLocation(int32_t l) { return (_estimatedBinaryLocation = l); }

    /**
     * @brief Gets source register
     * @return source register
     */
    TR::Register *getSource1Register() { return _source1Register; }

    /**
     * @brief Sets source register
     * @param[in] sr : source register
     * @return source register
     */
    TR::Register *setSource1Register(TR::Register *sr) { return (_source1Register = sr); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     * @param[in] distance : branch distance
     */
    void insertImmediateField(uint32_t *instruction, int32_t distance)
    {
        TR_ASSERT_FATAL((distance & 0x3) == 0, "branch distance is not aligned");
        *instruction |= ((distance >> 2) & 0x3fff) << 5;
    }

    /**
     * @brief Sets source register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource1Register(uint32_t *instruction)
    {
        TR::RealRegister *source1 = toRealRegister(_source1Register);
        source1->setRegisterFieldRD(instruction);
    }

    /**
     * @brief Sets bit position field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertBitposField(uint32_t *instruction)
    {
        TR_ASSERT_FATAL(_bitpos <= 63, "bit position must be 0 to 63");
        *instruction |= (((_bitpos >> 5) & 1) << 31) | ((_bitpos & 0x1f) << 19);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64RegBranchInstruction : public TR::Instruction {
    TR::Register *_register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : branch target
     * @param[in] cg : CodeGenerator
     */
    ARM64RegBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cg)
        , _register(treg)
    {
        useRegister(treg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : branch target
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64RegBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, precedingInstruction, cg)
        , _register(treg)
    {
        useRegister(treg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : branch target
     * @param[in] cond : register dependency condition
     * @param[in] cg : CodeGenerator
     */
    ARM64RegBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, cg)
        , _register(treg)
    {
        useRegister(treg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : branch target
     * @param[in] cond : register dependency condition
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64RegBranchInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, precedingInstruction, cg)
        , _register(treg)
    {
        useRegister(treg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsRegBranch; }

    /**
     * @brief Gets target register
     * @return target register
     */
    TR::Register *getTargetRegister() { return _register; }

    /**
     * @brief Sets target register
     * @param[in] tr : target register
     * @return target register
     */
    TR::Register *setTargetRegister(TR::Register *tr) { return (_register = tr); }

    /**
     * @brief Sets target register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertTargetRegister(uint32_t *instruction)
    {
        TR::RealRegister *target = toRealRegister(_register);
        target->setRegisterFieldRN(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64AdminInstruction : public TR::Instruction {
    TR::Node *_fenceNode;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] fenceNode : fence node
     * @param[in] cg : CodeGenerator
     */
    ARM64AdminInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Node *fenceNode, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cg)
        , _fenceNode(fenceNode)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] fenceNode : fence node
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64AdminInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Node *fenceNode,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, precedingInstruction, cg)
        , _fenceNode(fenceNode)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] cond : register dependency conditions
     * @param[in] node : node
     * @param[in] fenceNode : fence node
     * @param[in] cg : CodeGenerator
     */
    ARM64AdminInstruction(TR::InstOpCode::Mnemonic op, TR::RegisterDependencyConditions *cond, TR::Node *node,
        TR::Node *fenceNode, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, cg)
        , _fenceNode(fenceNode)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] cond : register dependency conditions
     * @param[in] node : node
     * @param[in] fenceNode : fence node
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64AdminInstruction(TR::InstOpCode::Mnemonic op, TR::RegisterDependencyConditions *cond, TR::Node *node,
        TR::Node *fenceNode, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, precedingInstruction, cg)
        , _fenceNode(fenceNode)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsAdmin; }

    /**
     * @brief Gets fence node
     * @return fence node
     */
    TR::Node *getFenceNode() { return _fenceNode; }

    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Expand this instruction prior to binary encoding.
     *
     * @returns the last instruction in the expansion or this instruction if no expansion was
     *          performed.
     */
    virtual TR::Instruction *expandInstruction();

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

class ARM64Trg1Instruction : public TR::Instruction {
    TR::Register *_target1Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cg)
        , _target1Register(treg)
    {
        useRegister(treg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, precedingInstruction, cg)
        , _target1Register(treg)
    {
        useRegister(treg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] cond : register dependency conditions
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, cg)
        , _target1Register(treg)
    {
        useRegister(treg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] cond : register dependency conditions
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cond, precedingInstruction, cg)
        , _target1Register(treg)
    {
        useRegister(treg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1; }

    /**
     * @brief Gets target register
     * @return target register
     */
    TR::Register *getTargetRegister() { return _target1Register; }

    /**
     * @brief Sets target register
     * @param[in] tr : target register
     * @return target register
     */
    TR::Register *setTargetRegister(TR::Register *tr) { return (_target1Register = tr); }

    /**
     * @brief Sets target register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertTargetRegister(uint32_t *instruction)
    {
        TR::RealRegister *target = toRealRegister(_target1Register);
        target->setRegisterFieldRD(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1CondInstruction : public ARM64Trg1Instruction {
    TR::ARM64ConditionCode _cc;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] cc : branch condition code
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1CondInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::ARM64ConditionCode cc,
        TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, cg)
        , _cc(cc)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] cc : branch condition code
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1CondInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::ARM64ConditionCode cc,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, precedingInstruction, cg)
        , _cc(cc)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Cond; }

    /**
     * @brief Gets condition code
     * @return condition code
     */
    TR::ARM64ConditionCode getConditionCode() { return _cc; }

    /**
     * @brief Sets condition code
     * @param[in] cc : condition code
     * @return condition code
     */
    TR::ARM64ConditionCode setConditionCode(TR::ARM64ConditionCode cc) { return (_cc = cc); }

    /**
     * @brief Sets condition code field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertConditionCodeField(uint32_t *instruction) { *instruction |= ((_cc & 0xf) << 12); }

    /**
     * @brief Sets zero register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertZeroRegister(uint32_t *instruction)
    {
        TR::RealRegister *zeroReg = cg()->machine()->getRealRegister(TR::RealRegister::xzr);
        zeroReg->setRegisterFieldRM(instruction);
        zeroReg->setRegisterFieldRN(instruction);
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1ImmInstruction : public ARM64Trg1Instruction {
    uint32_t _sourceImmediate;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, uint32_t imm,
        TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, cg)
        , _sourceImmediate(imm)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, uint32_t imm,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, precedingInstruction, cg)
        , _sourceImmediate(imm)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Imm; }

    /**
     * @brief Gets source immediate
     * @return source immediate
     */
    uint32_t getSourceImmediate() { return _sourceImmediate; }

    /**
     * @brief Sets source immediate
     * @param[in] si : immediate value
     * @return source immediate
     */
    uint32_t setSourceImmediate(uint32_t si) { return (_sourceImmediate = si); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertImmediateField(uint32_t *instruction)
    {
        TR::InstOpCode::Mnemonic op = getOpCodeValue();

        if (op == TR::InstOpCode::movzx || op == TR::InstOpCode::movzw || op == TR::InstOpCode::movnx
            || op == TR::InstOpCode::movnw || op == TR::InstOpCode::movkx || op == TR::InstOpCode::movkw) {
            *instruction |= ((_sourceImmediate & 0x3ffff) << 5);
        } else if (op == TR::InstOpCode::fmovimms || op == TR::InstOpCode::fmovimmd) {
            *instruction |= ((_sourceImmediate & 0xFF) << 13);
        } else if (op == TR::InstOpCode::adr || op == TR::InstOpCode::adrp) {
            *instruction |= ((_sourceImmediate & 0x1ffffc) << 3) | ((_sourceImmediate & 0x3) << 29);
        } else if (op == TR::InstOpCode::vmovi16b || op == TR::InstOpCode::vmovi8h || op == TR::InstOpCode::vmovi4s
            || op == TR::InstOpCode::vmovi4s_one || op == TR::InstOpCode::vmovi2s || op == TR::InstOpCode::movid
            || op == TR::InstOpCode::vmovi2d || op == TR::InstOpCode::vfmov4s || op == TR::InstOpCode::vfmov2d
            || op == TR::InstOpCode::vmvni8h || op == TR::InstOpCode::vmvni4s || op == TR::InstOpCode::vmvni4s_one
            || op == TR::InstOpCode::vbicimm8h || op == TR::InstOpCode::vbicimm4s || op == TR::InstOpCode::vorrimm8h
            || op == TR::InstOpCode::vorrimm4s) {
            *instruction |= ((_sourceImmediate & 0xe0) << 11) | ((_sourceImmediate & 0x1f) << 5);
        } else {
            TR_ASSERT(false, "Unsupported opcode in ARM64Trg1ImmInstruction.");
        }
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1ImmShiftedInstruction : public ARM64Trg1ImmInstruction {
    uint32_t _shiftAmount;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] imm : immediate value
     * @param[in] shiftAmount : shiftAmount
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1ImmShiftedInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, uint32_t imm,
        uint32_t shiftAmount, TR::CodeGenerator *cg)
        : ARM64Trg1ImmInstruction(op, node, treg, imm, cg)
        , _shiftAmount(shiftAmount)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] imm : immediate value
     * @param[in] shiftAmount : shiftAmount
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1ImmShiftedInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, uint32_t imm,
        uint32_t shiftAmount, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1ImmInstruction(op, node, treg, imm, precedingInstruction, cg)
        , _shiftAmount(shiftAmount)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1ImmShifted; }

    /**
     * @brief Gets shift amount
     * @return shift amount
     */
    uint32_t getShiftAmount() { return _shiftAmount; }

    /**
     * @brief Sets shift amount
     * @param[in] sa : shift amount
     * @return shift amount
     */
    uint32_t setShiftAmount(uint32_t sa) { return (_shiftAmount = sa); }

    /**
     * @brief Sets shift type and amount in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertShift(uint32_t *instruction)
    {
        TR::InstOpCode::Mnemonic op = getOpCodeValue();
        if ((op == TR::InstOpCode::vmovi8h) || (op == TR::InstOpCode::vmvni8h) || (op == TR::InstOpCode::vbicimm8h)
            || (op == TR::InstOpCode::vorrimm8h)) {
            TR_ASSERT_FATAL((_shiftAmount == 0) || (_shiftAmount == 8),
                "shiftAmount other than 0 or 8 is not allowed for vmovi8h, vmvni8h, vbicimm8h and vorrimm8h");
            *instruction |= (_shiftAmount >> 3) << 13;
        } else if ((op == TR::InstOpCode::vmovi4s) || (op == TR::InstOpCode::vmvni4s)
            || (op == TR::InstOpCode::vbicimm4s) || (op == TR::InstOpCode::vorrimm4s)) {
            TR_ASSERT_FATAL((_shiftAmount == 0) || (_shiftAmount == 8) || (_shiftAmount == 16) || (_shiftAmount == 24),
                "shiftAmount other than 0, 8, 16, or 24 is not allowed for vmovi4s, vmvni4s, vbicimm4s and vorrimm4s");
            *instruction |= (_shiftAmount >> 3) << 13;
        } else if ((op == TR::InstOpCode::vmovi4s_one) || (op == TR::InstOpCode::vmvni4s_one)) {
            TR_ASSERT_FATAL((_shiftAmount == 8) || (_shiftAmount == 16),
                "shiftAmount other than 8 or 16 is not allowed for vmovi8h and vmvni8h");

            *instruction |= (_shiftAmount >> 4) << 12;
        } else {
            TR_ASSERT_FATAL(false, "Unsupported opcode in ARM64Trg1ImmShiftedInstruction.");
        }
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1ImmSymInstruction : public ARM64Trg1ImmInstruction {
    TR::Symbol *_symbol;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] imm : immediate value
     * @param[in] sym : symbol
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1ImmSymInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, uint32_t imm,
        TR::Symbol *sym, TR::CodeGenerator *cg)
        : ARM64Trg1ImmInstruction(op, node, treg, imm, cg)
        , _symbol(sym)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] imm : immediate value
     * @param[in] sym : symbol
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1ImmSymInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, uint32_t imm,
        TR::Symbol *sym, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1ImmInstruction(op, node, treg, imm, precedingInstruction, cg)
        , _symbol(sym)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1ImmSym; }

    /**
     *
     * @brief Gets symbol
     * @return symbol
     */
    TR::Symbol *getSymbol() { return _symbol; }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertImmediateField(uint32_t *instruction)
    {
        TR::InstOpCode::Mnemonic op = getOpCodeValue();
        if (op == TR::InstOpCode::adr || op == TR::InstOpCode::adrp) {
            *instruction |= ((getSourceImmediate() & 0x1ffffc) << 3) | ((getSourceImmediate() & 0x3) << 29);
        } else {
            *instruction |= ((getSourceImmediate() & 0x7ffff) << 5);
        }
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1Src1Instruction : public ARM64Trg1Instruction {
    TR::Register *_source1Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, precedingInstruction, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] cond : register dependency conditions
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, cond, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] cond : register dependency conditions
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, cond, precedingInstruction, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src1; }

    /**
     * @brief Gets source register
     * @return source register
     */
    TR::Register *getSource1Register() { return _source1Register; }

    /**
     * @brief Sets source register
     * @param[in] sr : source register
     * @return source register
     */
    TR::Register *setSource1Register(TR::Register *sr) { return (_source1Register = sr); }

    /**
     * @brief Gets i-th source register
     * @param[in] i : index
     * @return i-th source register or NULL
     */
    virtual TR::Register *getSourceRegister(uint32_t i)
    {
        if (i == 0)
            return _source1Register;
        return NULL;
    }

    /**
     * @brief Sets source register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource1Register(uint32_t *instruction)
    {
        TR::RealRegister *source1 = toRealRegister(_source1Register);
        source1->setRegisterFieldRN(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

/*
 * This class is designated to be used for alias instruction such as movw, movx, negw, negx
 */
class ARM64Trg1ZeroSrc1Instruction : public ARM64Trg1Src1Instruction {
public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1ZeroSrc1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, sreg, cg)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1ZeroSrc1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, sreg, precedingInstruction, cg)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1ZeroSrc1; }

    /**
     * @brief Sets source register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource1Register(uint32_t *instruction)
    {
        TR::RealRegister *source1 = toRealRegister(getSource1Register());
        source1->setRegisterFieldRM(instruction);
    }

    /**
     * @brief Sets zero register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertZeroRegister(uint32_t *instruction)
    {
        TR::RealRegister *zeroReg = cg()->machine()->getRealRegister(TR::RealRegister::xzr);
        zeroReg->setRegisterFieldRN(instruction);
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

/*
 * This class is designated to be used for alias instruction such as movw, movx (bitmask immediate)
 */
class ARM64Trg1ZeroImmInstruction : public ARM64Trg1Instruction {
    uint32_t _source1Immediate;
    bool _Nbit;

public:
    /*
     * @brief Constructor
     * @param[in]   op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in]  imm : immediate value
     * @param[in]   cg : CodeGenerator
     */
    ARM64Trg1ZeroImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, uint32_t imm,
        TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, cg)
        , _source1Immediate(imm)
        , _Nbit(false)
    {}

    /*
     * @brief Constructor
     * @param[in]   op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in]    N : N bit value
     * @param[in]  imm : immediate value
     * @param[in]   cg : CodeGenerator
     */
    ARM64Trg1ZeroImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, bool N, uint32_t imm,
        TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, cg)
        , _source1Immediate(imm)
        , _Nbit(N)
    {}

    /*
     * @brief Constructor
     * @param[in]                   op : instruction opcode
     * @param[in]                 node : node
     * @param[in]                 treg : target register
     * @param[in]                  imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in]                   cg : CodeGenerator
     */
    ARM64Trg1ZeroImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, uint32_t imm,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, precedingInstruction, cg)
        , _source1Immediate(imm)
        , _Nbit(false)
    {}

    /*
     * @brief Constructor
     * @param[in]                   op : instruction opcode
     * @param[in]                 node : node
     * @param[in]                 treg : target register
     * @param[in]                    N : N bit value
     * @param[in]                  imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in]                   cg : CodeGenerator
     */
    ARM64Trg1ZeroImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, bool N, uint32_t imm,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Instruction(op, node, treg, precedingInstruction, cg)
        , _source1Immediate(imm)
        , _Nbit(N)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1ZeroImm; }

    /**
     * @brief Gets source immediate
     * @return source immediate
     */
    uint32_t getSourceImmediate() { return _source1Immediate; }

    /**
     * @brief Sets source immediate
     * @param[in] si : immediate value
     * @return source immediate
     */
    uint32_t setSourceImmediate(uint32_t si) { return (_source1Immediate = si); }

    /**
     * @brief Gets the N bit (bit 22)
     * @return N bit value
     */
    bool getNbit() { return _Nbit; }

    /**
     * @brief Sets the N bit (bit 22)
     * @param[in] n : N bit value
     * @return N bit value
     */
    bool setNbit(bool n) { return (_Nbit = n); }

    /**
     * @brief Sets zero register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertZeroRegister(uint32_t *instruction)
    {
        TR::RealRegister *zeroReg = cg()->machine()->getRealRegister(TR::RealRegister::xzr);
        zeroReg->setRegisterFieldRN(instruction);
    }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertImmediateField(uint32_t *instruction)
    {
        *instruction |= ((_source1Immediate & 0xfff) << 10); /* imm12 */
    }

    /**
     * @brief Sets N bit (bit 22) field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertNbit(uint32_t *instruction)
    {
        if (_Nbit) {
            *instruction |= (1 << 22);
        }
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1Src1ImmInstruction : public ARM64Trg1Src1Instruction {
    uint32_t _source1Immediate;
    bool _Nbit;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        uint32_t imm, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, sreg, cg)
        , _source1Immediate(imm)
        , _Nbit(false)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] N   : N bit value
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        bool N, uint32_t imm, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, sreg, cg)
        , _source1Immediate(imm)
        , _Nbit(N)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        uint32_t imm, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, sreg, precedingInstruction, cg)
        , _source1Immediate(imm)
        , _Nbit(false)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] N   : N bit value
     * @param[in] imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        bool N, uint32_t imm, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, sreg, precedingInstruction, cg)
        , _source1Immediate(imm)
        , _Nbit(N)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] imm : immediate value
     * @param[in] cond : register dependency conditions
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        uint32_t imm, TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, sreg, cond, cg)
        , _source1Immediate(imm)
        , _Nbit(false)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] sreg : source register
     * @param[in] imm : immediate value
     * @param[in] cond : register dependency conditions
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *sreg,
        uint32_t imm, TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction,
        TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, sreg, cond, precedingInstruction, cg)
        , _source1Immediate(imm)
        , _Nbit(false)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src1Imm; }

    /**
     * @brief Gets source immediate
     * @return source immediate
     */
    uint32_t getSourceImmediate() { return _source1Immediate; }

    /**
     * @brief Sets source immediate
     * @param[in] si : immediate value
     * @return source immediate
     */
    uint32_t setSourceImmediate(uint32_t si) { return (_source1Immediate = si); }

    /**
     * @brief Gets the N bit (bit 22)
     * @return N bit value
     */
    bool getNbit() { return _Nbit; }

    /**
     * @brief Sets the N bit (bit 22)
     * @param[in] n : N bit value
     * @return N bit value
     */
    bool setNbit(bool n) { return (_Nbit = n); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertImmediateField(uint32_t *instruction)
    {
        TR::InstOpCode::Mnemonic op = getOpCodeValue();

        if ((op >= TR::InstOpCode::vshl16b) && (op <= TR::InstOpCode::vsri2d)) {
            *instruction |= ((_source1Immediate & 0x7f) << 16); /* immh:immb */
        } else if ((op >= TR::InstOpCode::vdupe16b) && (op <= TR::InstOpCode::vinsxd)) {
            *instruction |= ((_source1Immediate & 0x1f) << 16); /* imm5 */
        } else if ((op >= TR::InstOpCode::vinseb) && (op <= TR::InstOpCode::vinsed)) {
            *instruction |= ((_source1Immediate & 0x3ff) << 11); /* imm5 and imm4 */
        } else {
            *instruction |= ((_source1Immediate & 0xfff) << 10); /* imm12 */
        }
    }

    /**
     * @brief Sets N bit (bit 22) field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertNbit(uint32_t *instruction)
    {
        if (_Nbit) {
            *instruction |= (1 << 22);
        }
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1Src2Instruction : public ARM64Trg1Src1Instruction {
    TR::Register *_source2Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, s1reg, cg)
        , _source2Register(s2reg)
    {
        useRegister(s2reg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, s1reg, precedingInstruction, cg)
        , _source2Register(s2reg)
    {
        useRegister(s2reg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cond : register dependency conditions
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, s1reg, cond, cg)
        , _source2Register(s2reg)
    {
        useRegister(s2reg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cond : register dependency conditions
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::RegisterDependencyConditions *cond, TR::Instruction *precedingInstruction,
        TR::CodeGenerator *cg)
        : ARM64Trg1Src1Instruction(op, node, treg, s1reg, cond, precedingInstruction, cg)
        , _source2Register(s2reg)
    {
        useRegister(s2reg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src2; }

    /**
     * @brief Gets source register 2
     * @return source register 2
     */
    TR::Register *getSource2Register() { return _source2Register; }

    /**
     * @brief Sets source register 2
     * @param[in] sr : source register 2
     * @return source register 2
     */
    TR::Register *setSource2Register(TR::Register *sr) { return (_source2Register = sr); }

    /**
     * @brief Gets i-th source register
     * @param[in] i : index
     * @return i-th source register or NULL
     */
    virtual TR::Register *getSourceRegister(uint32_t i)
    {
        if (i == 0)
            return getSource1Register();
        else if (i == 1)
            return _source2Register;
        return NULL;
    }

    /**
     * @brief Sets source register 2 in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource2Register(uint32_t *instruction)
    {
        TR::RealRegister *source2 = toRealRegister(_source2Register);
        source2->setRegisterFieldRM(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64CondTrg1Src2Instruction : public ARM64Trg1Src2Instruction {
    TR::ARM64ConditionCode _cc;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cc : branch condition code
     * @param[in] cg : CodeGenerator
     */
    ARM64CondTrg1Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::ARM64ConditionCode cc, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cg)
        , _cc(cc)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cc : branch condition code
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64CondTrg1Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::ARM64ConditionCode cc, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, precedingInstruction, cg)
        , _cc(cc)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cc : branch condition code
     * @param[in] cond : Register Dependency Condition
     * @param[in] cg : CodeGenerator
     */
    ARM64CondTrg1Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::ARM64ConditionCode cc, TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cond, cg)
        , _cc(cc)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cc : branch condition code
     * @param[in] cond : Register Dependency Condition
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64CondTrg1Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::ARM64ConditionCode cc, TR::RegisterDependencyConditions *cond,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cond, precedingInstruction, cg)
        , _cc(cc)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsCondTrg1Src2; }

    /**
     * @brief Gets condition code
     * @return condition code
     */
    TR::ARM64ConditionCode getConditionCode() { return _cc; }

    /**
     * @brief Sets condition code
     * @param[in] cc : condition code
     * @return condition code
     */
    TR::ARM64ConditionCode setConditionCode(TR::ARM64ConditionCode cc) { return (_cc = cc); }

    /**
     * @brief Sets condition code in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertConditionCodeField(uint32_t *instruction) { *instruction |= ((_cc & 0xf) << 12); }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1Src2ImmInstruction : public ARM64Trg1Src2Instruction {
    uint32_t _sourceImmediate; /* imm4 */

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, uint32_t imm, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cg)
        , _sourceImmediate(imm)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, uint32_t imm, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, precedingInstruction, cg)
        , _sourceImmediate(imm)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src2Imm; }

    /**
     * @brief Gets source immediate
     * @return source immediate
     */
    uint32_t getSourceImmediate() { return _sourceImmediate; }

    /**
     * @brief Sets source immediate
     * @param[in] si : immediate value
     * @return source immediate
     */
    uint32_t setSourceImmediate(uint32_t si) { return (_sourceImmediate = si); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertImmediateField(uint32_t *instruction) { *instruction |= ((_sourceImmediate & 0xf) << 11); }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1Src2ShiftedInstruction : public ARM64Trg1Src2Instruction {
    ARM64ShiftCode _shiftType;
    uint32_t _shiftAmount; /* imm6 */

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] shiftType : shift type
     * @param[in] shiftAmount : shift amount
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2ShiftedInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::Register *s1reg, TR::Register *s2reg, ARM64ShiftCode shiftType, uint32_t shiftAmount, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cg)
        , _shiftType(shiftType)
        , _shiftAmount(shiftAmount)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] shiftType : shift type
     * @param[in] shiftAmount : shift amount
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2ShiftedInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::Register *s1reg, TR::Register *s2reg, ARM64ShiftCode shiftType, uint32_t shiftAmount,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, precedingInstruction, cg)
        , _shiftType(shiftType)
        , _shiftAmount(shiftAmount)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src2Shifted; }

    /**
     * @brief Gets shift type
     * @return shift type
     */
    ARM64ShiftCode getShiftType() { return _shiftType; }

    /**
     * @brief Sets shift type
     * @param[in] st : shift type
     * @return shift type
     */
    ARM64ShiftCode setShiftType(ARM64ShiftCode st) { return (_shiftType = st); }

    /**
     * @brief Gets shift amount
     * @return shift amount
     */
    uint32_t getShiftAmount() { return _shiftAmount; }

    /**
     * @brief Sets shift amount
     * @param[in] st : shift amount
     * @return shift amount
     */
    uint32_t setShiftAmount(uint32_t sa) { return (_shiftAmount = sa); }

    /**
     * @brief Sets shift type and amount in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertShift(uint32_t *instruction)
    {
        *instruction |= ((_shiftType & 0x3) << 22) | ((_shiftAmount & 0x3f) << 10);
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1Src2ExtendedInstruction : public ARM64Trg1Src2Instruction {
    ARM64ExtendCode _extendType;
    uint32_t _shiftAmount; /* imm3 */

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] extendType : extend type
     * @param[in] shiftAmount : shift amount
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2ExtendedInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::Register *s1reg, TR::Register *s2reg, ARM64ExtendCode extendType, uint32_t shiftAmount,
        TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cg)
        , _extendType(extendType)
        , _shiftAmount(shiftAmount)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] extendType : extend type
     * @param[in] shiftAmount : shift amount
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2ExtendedInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::Register *s1reg, TR::Register *s2reg, ARM64ExtendCode extendType, uint32_t shiftAmount,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, precedingInstruction, cg)
        , _extendType(extendType)
        , _shiftAmount(shiftAmount)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src2Extended; }

    /**
     * @brief Gets extend type
     * @return extend type
     */
    ARM64ExtendCode getExtendType() { return _extendType; }

    /**
     * @brief Sets extend type
     * @param[in] st : extend type
     * @return extend type
     */
    ARM64ExtendCode setExtendType(ARM64ExtendCode et) { return (_extendType = et); }

    /**
     * @brief Gets shift amount
     * @return shift amount
     */
    uint32_t getShiftAmount() { return _shiftAmount; }

    /**
     * @brief Sets shift amount
     * @param[in] st : shift amount
     * @return shift amount
     */
    uint32_t setShiftAmount(uint32_t sa) { return (_shiftAmount = sa); }

    /**
     * @brief Sets extend type and amount in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertExtend(uint32_t *instruction)
    {
        *instruction |= ((_extendType & 0x7) << 13) | ((_shiftAmount & 0x7) << 10);
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1Src2IndexedElementInstruction : public ARM64Trg1Src2Instruction {
    uint32_t _index;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] index : index of element in s2reg
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2IndexedElementInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::Register *s1reg, TR::Register *s2reg, uint32_t index, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cg)
        , _index(index)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] index : index of element in s2reg
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2IndexedElementInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::Register *s1reg, TR::Register *s2reg, uint32_t index, TR::Instruction *precedingInstruction,
        TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, precedingInstruction, cg)
        , _index(index)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src2IndexedElement; }

    /**
     * @brief Gets index
     * @return index of element in s2reg
     */
    uint32_t getIndex() { return _index; }

    /**
     * @brief Sets index in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertIndex(uint32_t *instruction);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

/*
 * This class is designated to be used for alias instruction such as mulw, mulx
 */
class ARM64Trg1Src2ZeroInstruction : public ARM64Trg1Src2Instruction {
public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2ZeroInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cg)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src2ZeroInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, precedingInstruction, cg)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src2Zero; }

    /**
     * @brief Sets zero register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertZeroRegister(uint32_t *instruction)
    {
        TR::RealRegister *zeroReg = cg()->machine()->getRealRegister(TR::RealRegister::xzr);
        zeroReg->setRegisterFieldRA(instruction);
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1Src3Instruction : public ARM64Trg1Src2Instruction {
    TR::Register *_source3Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] s3reg : source register 3
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src3Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::Register *s3reg, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cg)
        , _source3Register(s3reg)
    {
        useRegister(s3reg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] s3reg : source register 3
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src3Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::Register *s3reg, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, precedingInstruction, cg)
        , _source3Register(s3reg)
    {
        useRegister(s3reg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] s3reg : source register 3
     * @param[in] cond : register dependency conditions
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src3Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::Register *s3reg, TR::RegisterDependencyConditions *cond, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cond, cg)
        , _source3Register(s3reg)
    {
        useRegister(s3reg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] s3reg : source register 3
     * @param[in] cond : register dependency conditions
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1Src3Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::Register *s1reg,
        TR::Register *s2reg, TR::Register *s3reg, TR::RegisterDependencyConditions *cond,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1Src2Instruction(op, node, treg, s1reg, s2reg, cond, precedingInstruction, cg)
        , _source3Register(s3reg)
    {
        useRegister(s3reg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Src3; }

    /**
     * @brief Gets source register 3
     * @return source register 3
     */
    TR::Register *getSource3Register() { return _source3Register; }

    /**
     * @brief Sets source register 3
     * @param[in] sr : source register 3
     * @return source register 3
     */
    TR::Register *setSource3Register(TR::Register *sr) { return (_source3Register = sr); }

    /**
     * @brief Gets i-th source register
     * @param[in] i : index
     * @return i-th source register or NULL
     */
    virtual TR::Register *getSourceRegister(uint32_t i)
    {
        if (i == 0)
            return getSource1Register();
        else if (i == 1)
            return getSource2Register();
        else if (i == 2)
            return _source3Register;
        return NULL;
    }

    /**
     * @brief Sets source register 3 in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource3Register(uint32_t *instruction)
    {
        TR::RealRegister *source3 = toRealRegister(_source3Register);
        source3->setRegisterFieldRA(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Trg1MemInstruction : public ARM64Trg1Instruction {
    TR::MemoryReference *_memoryReference;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] mr : memory reference
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1MemInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::MemoryReference *mr,
        TR::CodeGenerator *cg);

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] mr : memory reference
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1MemInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg, TR::MemoryReference *mr,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg);

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1Mem; }

    /**
     * @brief Gets memory reference
     * @return memory reference
     */
    TR::MemoryReference *getMemoryReference() { return _memoryReference; }

    /**
     * @brief Sets memory reference
     * @param[in] mr : memory reference
     * @return memory reference
     */
    TR::MemoryReference *setMemoryReference(TR::MemoryReference *mr) { return (_memoryReference = mr); }

    virtual TR::Snippet *getSnippetForGC() { return getMemoryReference()->getUnresolvedSnippet(); }

    /**
     * @brief Gets base register of memory reference
     * @return base register
     */
    virtual TR::Register *getMemoryBase() { return getMemoryReference()->getBaseRegister(); }

    /**
     * @brief Gets index register of memory reference
     * @return index register
     */
    virtual TR::Register *getMemoryIndex() { return getMemoryReference()->getIndexRegister(); }

    /**
     * @brief Gets offset of memory reference
     * @return offset
     */
    virtual int32_t getOffset() { return getMemoryReference()->getOffset(); }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Expand this instruction prior to binary encoding.
     *
     * @returns the last instruction in the expansion or this instruction if no expansion was
     *          performed.
     */
    virtual TR::Instruction *expandInstruction();

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

class ARM64Trg2MemInstruction : public ARM64Trg1MemInstruction {
    TR::Register *_target2Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg1 : target register 1
     * @param[in] treg2 : target register 2
     * @param[in] mr : memory reference
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg2MemInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg1, TR::Register *treg2,
        TR::MemoryReference *mr, TR::CodeGenerator *cg)
        : ARM64Trg1MemInstruction(op, node, treg1, mr, cg)
        , _target2Register(treg2)
    {
        useRegister(treg2);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register 1
     * @param[in] treg2 : target register 2
     * @param[in] mr : memory reference
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg2MemInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg1, TR::Register *treg2,
        TR::MemoryReference *mr, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1MemInstruction(op, node, treg1, mr, precedingInstruction, cg)
        , _target2Register(treg2)
    {
        useRegister(treg2);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg2Mem; }

    /**
     * @brief Gets target register 2
     * @return target register 2
     */
    TR::Register *getTarget2Register() { return _target2Register; }

    /**
     * @brief Sets target register 2
     * @param[in] tr : target register 2
     * @return target register 2
     */
    TR::Register *setTarget2Register(TR::Register *tr) { return (_target2Register = tr); }

    /**
     * @brief Sets target 2 register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertTarget2Register(uint32_t *instruction)
    {
        TR::RealRegister *target2 = toRealRegister(_target2Register);
        target2->setRegisterFieldRT2(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

class ARM64MemInstruction : public TR::Instruction {
    TR::MemoryReference *_memoryReference;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] mr : memory reference
     * @param[in] cg : CodeGenerator
     */
    ARM64MemInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::MemoryReference *mr, TR::CodeGenerator *cg);

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] mr : memory reference
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64MemInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::MemoryReference *mr,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg);

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsMem; }

    /**
     * @brief Gets memory reference
     * @return memory reference
     */
    TR::MemoryReference *getMemoryReference() { return _memoryReference; }

    /**
     * @brief Sets memory reference
     * @param[in] mr : memory reference
     * @return memory reference
     */
    TR::MemoryReference *setMemoryReference(TR::MemoryReference *mr) { return (_memoryReference = mr); }

    /**
     * @brief Gets base register of memory reference
     * @return base register
     */
    virtual TR::Register *getMemoryBase() { return getMemoryReference()->getBaseRegister(); }

    /**
     * @brief Gets index register of memory reference
     * @return index register
     */
    virtual TR::Register *getMemoryIndex() { return getMemoryReference()->getIndexRegister(); }

    /**
     * @brief Gets offset of memory reference
     * @return offset
     */
    virtual int32_t getOffset() { return getMemoryReference()->getOffset(); }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Expand this instruction prior to binary encoding.
     *
     * @returns the last instruction in the expansion or this instruction if no expansion was
     *          performed.
     */
    virtual TR::Instruction *expandInstruction();

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

class ARM64MemSrc1Instruction : public ARM64MemInstruction {
    TR::Register *_source1Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] mr : memory reference
     * @param[in] sreg : source register
     * @param[in] cg : CodeGenerator
     */
    ARM64MemSrc1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::MemoryReference *mr, TR::Register *sreg,
        TR::CodeGenerator *cg)
        : ARM64MemInstruction(op, node, mr, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] mr : memory reference
     * @param[in] sreg : source register
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64MemSrc1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::MemoryReference *mr, TR::Register *sreg,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64MemInstruction(op, node, mr, precedingInstruction, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsMemSrc1; }

    /**
     * @brief Gets source register
     * @return source register
     */
    TR::Register *getSource1Register() { return _source1Register; }

    /**
     * @brief Sets source register
     * @param[in] sr : source register
     * @return source register
     */
    TR::Register *setSource1Register(TR::Register *sr) { return (_source1Register = sr); }

    virtual TR::Snippet *getSnippetForGC() { return getMemoryReference()->getUnresolvedSnippet(); }

    /**
     * @brief Sets source register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource1Register(uint32_t *instruction)
    {
        TR::RealRegister *source1 = toRealRegister(_source1Register);
        source1->setRegisterFieldRT(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

class ARM64MemSrc2Instruction : public ARM64MemSrc1Instruction {
    TR::Register *_source2Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] mr : memory reference
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cg : CodeGenerator
     */
    ARM64MemSrc2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::MemoryReference *mr, TR::Register *s1reg,
        TR::Register *s2reg, TR::CodeGenerator *cg)
        : ARM64MemSrc1Instruction(op, node, mr, s1reg, cg)
        , _source2Register(s2reg)
    {
        useRegister(s2reg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] mr : memory reference
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64MemSrc2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::MemoryReference *mr, TR::Register *s1reg,
        TR::Register *s2reg, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64MemSrc1Instruction(op, node, mr, s1reg, precedingInstruction, cg)
        , _source2Register(s2reg)
    {
        useRegister(s2reg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsMemSrc2; }

    /**
     * @brief Gets source register 2
     * @return source register 2
     */
    TR::Register *getSource2Register() { return _source2Register; }

    /**
     * @brief Sets source register 2
     * @param[in] sr : source register 2
     * @return source register 2
     */
    TR::Register *setSource2Register(TR::Register *sr) { return (_source2Register = sr); }

    virtual TR::Snippet *getSnippetForGC() { return getMemoryReference()->getUnresolvedSnippet(); }

    /**
     * @brief Sets source register 2 in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource2Register(uint32_t *instruction)
    {
        TR::RealRegister *source2 = toRealRegister(_source2Register);
        source2->setRegisterFieldRT2(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

// for "store exclusive" instructions such as stxrx/stxrw
class ARM64Trg1MemSrc1Instruction : public ARM64Trg1MemInstruction {
    TR::Register *_source1Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] mr : memory reference
     * @param[in] sreg : source register
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1MemSrc1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::MemoryReference *mr, TR::Register *sreg, TR::CodeGenerator *cg)
        : ARM64Trg1MemInstruction(op, node, treg, mr, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] treg : target register
     * @param[in] mr : memory reference
     * @param[in] sreg : source register
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Trg1MemSrc1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *treg,
        TR::MemoryReference *mr, TR::Register *sreg, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Trg1MemInstruction(op, node, treg, mr, precedingInstruction, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsTrg1MemSrc1; }

    /**
     * @brief Gets source register
     * @return source register
     */
    TR::Register *getSource1Register() { return _source1Register; }

    /**
     * @brief Sets source register
     * @param[in] sr : source register
     * @return source register
     */
    TR::Register *setSource1Register(TR::Register *sr) { return (_source1Register = sr); }

    virtual TR::Snippet *getSnippetForGC() { return getMemoryReference()->getUnresolvedSnippet(); }

    /**
     * @brief Sets target register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertTargetRegister(uint32_t *instruction)
    {
        TR::RealRegister *target = toRealRegister(getTargetRegister());
        target->setRegisterFieldRS(instruction);
    }

    /**
     * @brief Sets source register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource1Register(uint32_t *instruction)
    {
        TR::RealRegister *source1 = toRealRegister(_source1Register);
        source1->setRegisterFieldRT(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();

    /**
     * @brief Estimates binary length
     * @param[in] currentEstimate : current estimated length
     * @return estimated binary length
     */
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);
};

class ARM64MemImmInstruction : public ARM64MemInstruction {
    uint32_t _immediate; // Imm5

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] mr : memory reference
     * @param[in] immediate : 5bit immediate
     * @param[in] cg : CodeGenerator
     */
    ARM64MemImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::MemoryReference *mr, uint32_t immediate,
        TR::CodeGenerator *cg)
        : ARM64MemInstruction(op, node, mr, cg)
        , _immediate(immediate)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] mr : memory reference
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] immediate : 5bit immediate
     * @param[in] cg : CodeGenerator
     */
    ARM64MemImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::MemoryReference *mr, uint32_t immediate,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64MemInstruction(op, node, mr, precedingInstruction, cg)
        , _immediate(immediate)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsMemImm; }

    /**
     * @brief Gets immediate
     * @return immediate
     */
    uint32_t getImmediate() { return _immediate; }

    /**
     * @brief Sets immediate
     * @param[in] immediate : immediate value
     * @return immediate
     */
    uint32_t setImmediate(uint32_t immediate) { return (_immediate = immediate); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertImmediateField(uint32_t *instruction) { *instruction |= (_immediate & 0x1f); /* imm5 */ }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Src1Instruction : public TR::Instruction {
    TR::Register *_source1Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] cg : CodeGenerator
     */
    ARM64Src1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Src1Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::Instruction(op, node, precedingInstruction, cg)
        , _source1Register(sreg)
    {
        useRegister(sreg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsSrc1; }

    /**
     * @brief Gets source register
     * @return source register
     */
    TR::Register *getSource1Register() { return _source1Register; }

    /**
     * @brief Sets source register
     * @param[in] tr : source register
     * @return source register
     */
    TR::Register *setSource1Register(TR::Register *tr) { return (_source1Register = tr); }

    /**
     * @brief Gets i-th source register
     * @param[in] i : index
     * @return i-th source register or NULL
     */
    virtual TR::Register *getSourceRegister(uint32_t i)
    {
        if (i == 0)
            return _source1Register;
        return NULL;
    }

    /**
     * @brief Sets source register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource1Register(uint32_t *instruction)
    {
        TR::RealRegister *source = toRealRegister(_source1Register);
        source->setRegisterFieldRN(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

/*
 * This class is designated to be used for alias instruction such as cmpimmw, cmpimmx, tstimmw, tstimmx
 */
class ARM64ZeroSrc1ImmInstruction : public ARM64Src1Instruction {
    uint32_t _source1Immediate;
    bool _Nbit;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64ZeroSrc1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, uint32_t imm,
        TR::CodeGenerator *cg)
        : ARM64Src1Instruction(op, node, sreg, cg)
        , _source1Immediate(imm)
        , _Nbit(false)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] N   : N bit value
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64ZeroSrc1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, bool N, uint32_t imm,
        TR::CodeGenerator *cg)
        : ARM64Src1Instruction(op, node, sreg, cg)
        , _source1Immediate(imm)
        , _Nbit(N)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64ZeroSrc1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, uint32_t imm,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Src1Instruction(op, node, sreg, precedingInstruction, cg)
        , _source1Immediate(imm)
        , _Nbit(false)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] sreg : source register
     * @param[in] N   : N bit value
     * @param[in] imm : immediate value
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64ZeroSrc1ImmInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *sreg, bool N, uint32_t imm,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Src1Instruction(op, node, sreg, precedingInstruction, cg)
        , _source1Immediate(imm)
        , _Nbit(N)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsZeroSrc1Imm; }

    /**
     * @brief Gets source immediate
     * @return source immediate
     */
    uint32_t getSourceImmediate() { return _source1Immediate; }

    /**
     * @brief Sets source immediate
     * @param[in] si : immediate value
     * @return source immediate
     */
    uint32_t setSourceImmediate(uint32_t si) { return (_source1Immediate = si); }

    /**
     * @brief Gets the N bit (bit 22)
     * @return N bit value
     */
    bool getNbit() { return _Nbit; }

    /**
     * @brief Sets the N bit (bit 22)
     * @param[in] n : N bit value
     * @return N bit value
     */
    bool setNbit(bool n) { return (_Nbit = n); }

    /**
     * @brief Sets zero register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertZeroRegister(uint32_t *instruction)
    {
        TR::RealRegister *zeroReg = cg()->machine()->getRealRegister(TR::RealRegister::xzr);
        zeroReg->setRegisterFieldRD(instruction);
    }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertImmediateField(uint32_t *instruction)
    {
        *instruction |= ((_source1Immediate & 0xfff) << 10); /* imm12 */
    }

    /**
     * @brief Sets N bit (bit 22) field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertNbit(uint32_t *instruction)
    {
        if (_Nbit) {
            *instruction |= (1 << 22);
        }
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Src2Instruction : public ARM64Src1Instruction {
    TR::Register *_source2Register;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cg : CodeGenerator
     */
    ARM64Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *s1reg, TR::Register *s2reg,
        TR::CodeGenerator *cg)
        : ARM64Src1Instruction(op, node, s1reg, cg)
        , _source2Register(s2reg)
    {
        useRegister(s2reg);
    }

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Src2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *s1reg, TR::Register *s2reg,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Src1Instruction(op, node, s1reg, precedingInstruction, cg)
        , _source2Register(s2reg)
    {
        useRegister(s2reg);
    }

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsSrc2; }

    /**
     * @brief Gets source register 2
     * @return source register 2
     */
    TR::Register *getSource2Register() { return _source2Register; }

    /**
     * @brief Sets source register 2
     * @param[in] sr : source register 2
     * @return source register 2
     */
    TR::Register *setSource2Register(TR::Register *sr) { return (_source2Register = sr); }

    /**
     * @brief Gets i-th source register
     * @param[in] i : index
     * @return i-th source register or NULL
     */
    virtual TR::Register *getSourceRegister(uint32_t i)
    {
        if (i == 0)
            return getSource1Register();
        else if (i == 1)
            return _source2Register;
        return NULL;
    }

    /**
     * @brief Sets source register 2 in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertSource2Register(uint32_t *instruction)
    {
        TR::RealRegister *source2 = toRealRegister(_source2Register);
        source2->setRegisterFieldRM(instruction);
    }

    /**
     * @brief Answers whether this instruction references the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction references the virtual register
     */
    virtual bool refsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction uses the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction uses the virtual register
     */
    virtual bool usesRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given virtual register
     * @param[in] reg : virtual register
     * @return true when the instruction defines the virtual register
     */
    virtual bool defsRegister(TR::Register *reg);
    /**
     * @brief Answers whether this instruction defines the given real register
     * @param[in] reg : real register
     * @return true when the instruction defines the real register
     */
    virtual bool defsRealRegister(TR::Register *reg);
    /**
     * @brief Assigns registers
     * @param[in] kindToBeAssigned : register kind
     */
    virtual void assignRegisters(TR_RegisterKinds kindToBeAssigned);

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

/*
 * This class is designated to be used for alias instruction such as cmpw, cmpx, tstw, tstx
 */
class ARM64ZeroSrc2Instruction : public ARM64Src2Instruction {
public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cg : CodeGenerator
     */
    ARM64ZeroSrc2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *s1reg, TR::Register *s2reg,
        TR::CodeGenerator *cg)
        : ARM64Src2Instruction(op, node, s1reg, s2reg, cg)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64ZeroSrc2Instruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *s1reg, TR::Register *s2reg,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64Src2Instruction(op, node, s1reg, s2reg, precedingInstruction, cg)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsZeroSrc2; }

    /**
     * @brief Sets zero register in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertZeroRegister(uint32_t *instruction)
    {
        TR::RealRegister *zeroReg = cg()->machine()->getRealRegister(TR::RealRegister::xzr);
        zeroReg->setRegisterFieldRD(instruction);
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64ConditionalCompareOp {
    TR::ARM64ConditionCode _cc;
    uint32_t _conditionFlags;

public:
    ARM64ConditionalCompareOp(TR::ARM64ConditionCode cc, uint32_t conditionFlags)
        : _cc(cc)
        , _conditionFlags(conditionFlags)
    {}

    /**
     * @brief Gets condition code
     * @return condition code
     */
    TR::ARM64ConditionCode getConditionCode() { return _cc; }

    /**
     * @brief Sets condition code
     * @param[in] cc : condition code
     * @return condition code
     */
    TR::ARM64ConditionCode setConditionCode(TR::ARM64ConditionCode cc) { return (_cc = cc); }

    /**
     * @brief Get the value of the condition flags
     * @return the value of the condition flags
     */
    uint32_t getConditionFlags() { return _conditionFlags; }

    /**
     * @brief Sets the condition flags
     * @param[in] conditionFlags : condition flags
     * @return the value of the condition flags
     */
    uint32_t setConditionFlags(uint32_t conditionFlags) { return (_conditionFlags = conditionFlags); }

    /**
     * @brief Sets condition code field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertConditionCodeField(uint32_t *instruction) { *instruction |= ((_cc & 0xf) << 12); }

    /**
     * @brief Sets condition flags in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertConditionFlags(uint32_t *instruction)
    {
        TR_ASSERT_FATAL(constantIsUnsignedImm4(getConditionFlags()), "Condition flags exceeds 4 bits.");
        *instruction |= (_conditionFlags & 0xf);
    }
};

class ARM64Src1ImmCondInstruction
    : public ARM64Src1Instruction
    , public ARM64ConditionalCompareOp {
    uint32_t _sourceImmediate;

public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] s1reg : source register 1
     * @param[in] imm : immediate value
     * @param[in] cc : branch condition code
     * @param[in] conditionFlags : 4-bit NZCV condition flags
     * @param[in] cg : CodeGenerator
     */
    ARM64Src1ImmCondInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *s1reg, uint32_t imm,
        TR::ARM64ConditionCode cc, uint32_t conditionFlags, TR::CodeGenerator *cg)
        : ARM64Src1Instruction(op, node, s1reg, cg)
        , ARM64ConditionalCompareOp(cc, conditionFlags)
        , _sourceImmediate(imm)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] s1reg : source register 1
     * @param[in] imm : immediate value
     * @param[in] cc : branch condition code
     * @param[in] conditionFlags : 4-bit NZCV condition flags
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Src1ImmCondInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *s1reg, uint32_t imm,
        TR::ARM64ConditionCode cc, uint32_t conditionFlags, TR::Instruction *precedingInstruction,
        TR::CodeGenerator *cg)
        : ARM64Src1Instruction(op, node, s1reg, precedingInstruction, cg)
        , ARM64ConditionalCompareOp(cc, conditionFlags)
        , _sourceImmediate(imm)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsSrc1ImmCond; }

    /**
     * @brief Gets source immediate
     * @return source immediate
     */
    uint32_t getSourceImmediate() { return _sourceImmediate; }

    /**
     * @brief Sets source immediate
     * @param[in] si : immediate value
     * @return source immediate
     */
    uint32_t setSourceImmediate(uint32_t si) { return (_sourceImmediate = si); }

    /**
     * @brief Sets immediate field in binary encoding
     * @param[in] instruction : instruction cursor
     */
    void insertImmediateField(uint32_t *instruction)
    {
        TR_ASSERT_FATAL(constantIsUnsignedImm5(getSourceImmediate()), "Immediate value exceeds 5 bits.");
        *instruction |= ((getSourceImmediate() & 0x1f) << 16);
    }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64Src2CondInstruction
    : public ARM64Src2Instruction
    , public ARM64ConditionalCompareOp {
public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cc : branch condition code
     * @param[in] conditionFlags : 4-bit NZCV condition flags
     * @param[in] cg : CodeGenerator
     */
    ARM64Src2CondInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *s1reg, TR::Register *s2reg,
        TR::ARM64ConditionCode cc, uint32_t conditionFlags, TR::CodeGenerator *cg)
        : ARM64Src2Instruction(op, node, s1reg, s2reg, cg)
        , ARM64ConditionalCompareOp(cc, conditionFlags)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] s1reg : source register 1
     * @param[in] s2reg : source register 2
     * @param[in] cc : branch condition code
     * @param[in] conditionFlags : 4-bit NZCV condition flags
     * @param[in] precedingInstruction : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64Src2CondInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, TR::Register *s1reg, TR::Register *s2reg,
        TR::ARM64ConditionCode cc, uint32_t conditionFlags, TR::Instruction *precedingInstruction,
        TR::CodeGenerator *cg)
        : ARM64Src2Instruction(op, node, s1reg, s2reg, precedingInstruction, cg)
        , ARM64ConditionalCompareOp(cc, conditionFlags)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsSrc2Cond; }

    /**
     * @brief Generates binary encoding of the instruction
     * @return instruction cursor
     */
    virtual uint8_t *generateBinaryEncoding();
};

class ARM64SynchronizationInstruction : public ARM64ImmInstruction {
public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64SynchronizationInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node,
        TR::InstOpCode::AArch64BarrierLimitation lim, TR::CodeGenerator *cg)
        : ARM64ImmInstruction(op, node, static_cast<uint32_t>(lim), cg)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] preced : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64SynchronizationInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node,
        TR::InstOpCode::AArch64BarrierLimitation lim, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64ImmInstruction(op, node, static_cast<uint32_t>(lim), precedingInstruction, cg)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsSynchronization; }

    /**
     * @brief Encodes the immediate value into the instruction
     * @param[in] instruction : instruction address
     */
    virtual void insertImmediateField(uint32_t *instruction)
    {
        TR_ASSERT_FATAL(constantIsUnsignedImm4(getSourceImmediate()), "Immediate value exceeds 4 bits.");
        *instruction |= ((getSourceImmediate() & 0xF) << 8);
    }
};

class ARM64ExceptionInstruction : public ARM64ImmInstruction {
public:
    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] cg : CodeGenerator
     */
    ARM64ExceptionInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uint32_t imm, TR::CodeGenerator *cg)
        : ARM64ImmInstruction(op, node, imm, cg)
    {}

    /*
     * @brief Constructor
     * @param[in] op : instruction opcode
     * @param[in] node : node
     * @param[in] imm : immediate value
     * @param[in] preced : preceding instruction
     * @param[in] cg : CodeGenerator
     */
    ARM64ExceptionInstruction(TR::InstOpCode::Mnemonic op, TR::Node *node, uint32_t imm,
        TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : ARM64ImmInstruction(op, node, imm, precedingInstruction, cg)
    {}

    /**
     * @brief Gets instruction kind
     * @return instruction kind
     */
    virtual Kind getKind() { return IsException; }

    /**
     * @brief Encodes the immediate value into the instruction
     * @param[in] instruction : instruction address
     */
    virtual void insertImmediateField(uint32_t *instruction)
    {
        TR_ASSERT_FATAL(constantIsUnsignedImm16(getSourceImmediate()), "Immediate value exceeds 16 bits.");
        *instruction |= ((getSourceImmediate() & 0xFFFF) << 5);
    }
};

#ifdef J9_PROJECT_SPECIFIC
class ARM64VirtualGuardNOPInstruction : public TR::ARM64LabelInstruction {
private:
    TR_VirtualGuardSite *_site;

public:
    ARM64VirtualGuardNOPInstruction(TR::Node *node, TR_VirtualGuardSite *site, TR::RegisterDependencyConditions *cond,
        TR::LabelSymbol *sym, TR::CodeGenerator *cg)
        : TR::ARM64LabelInstruction(TR::InstOpCode::vgnop, node, sym, cond, cg)
        , _site(site)
    {}

    ARM64VirtualGuardNOPInstruction(TR::Node *node, TR_VirtualGuardSite *site, TR::RegisterDependencyConditions *cond,
        TR::LabelSymbol *sym, TR::Instruction *precedingInstruction, TR::CodeGenerator *cg)
        : TR::ARM64LabelInstruction(TR::InstOpCode::vgnop, node, sym, cond, precedingInstruction, cg)
        , _site(site)
    {}

    virtual Kind getKind() { return IsVirtualGuardNOP; }

    void setSite(TR_VirtualGuardSite *site) { _site = site; }

    TR_VirtualGuardSite *getSite() { return _site; }

    virtual uint8_t *generateBinaryEncoding();
    virtual int32_t estimateBinaryLength(int32_t currentEstimate);

    virtual bool isVirtualGuardNOPInstruction() { return true; }
};
#endif

} // namespace TR

#endif
