/*
 * Copyright (C) 2015-2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#pragma once

#if ENABLE(B3_JIT)

#include "AirOpcode.h"
#include "AirTmp.h"
#include "B3Bank.h"
#include "B3Common.h"
#include "B3Type.h"
#include "B3Value.h"
#include "B3Width.h"
#include "Fits.h"
#include "OpcodeSize.h"

#if !ASSERT_ENABLED
IGNORE_RETURN_TYPE_WARNINGS_BEGIN
#endif

namespace JSC { namespace B3 {

class Value;

namespace Air {

class Special;
class StackSlot;

// This class name is also intentionally terse because we will say it a lot. You'll see code like
// Inst(..., Arg::imm(5), Arg::addr(thing, blah), ...)
class Arg {
public:
    // These enum members are intentionally terse because we have to mention them a lot.
    enum Kind : int8_t {
        Invalid,

        // This is either an unassigned temporary or a register. All unassigned temporaries
        // eventually become registers.
        Tmp,

        // This is an immediate that the instruction will materialize. Imm is the immediate that can be
        // inlined into most instructions, while BigImm indicates a constant materialization and is
        // usually only usable with Move. Specials may also admit it, for example for stackmaps used for
        // OSR exit and tail calls.
        // BitImm is an immediate for Bitwise operation (And, Xor, etc).
        Imm,
        BigImm,
        BitImm,
        BitImm64,
        FPImm32,
        FPImm64,
        FPImm128,

        // These are the addresses. Instructions may load from (Use), store to (Def), or evaluate
        // (UseAddr) addresses.
        SimpleAddr,
        Addr,
        ExtendedOffsetAddr,
        Stack,
        CallArg,
        Index,
        PreIndex,
        PostIndex,

        // Immediate operands that customize the behavior of an operation. You can think of them as
        // secondary opcodes. They are always "Use"'d.
        RelCond,
        ResCond,
        DoubleCond,
        StatusCond,
        Special,
        WidthArg,

        // ZeroReg is interpreted as a zero register in ARM64
        ZeroReg,
        
        SIMDInfo
    };
    
    enum Temperature : int8_t {
        Cold,
        Warm
    };
    
    enum Phase : int8_t {
        Early,
        Late
    };
    
    enum Timing : int8_t {
        OnlyEarly,
        OnlyLate,
        EarlyAndLate
    };

    enum Role : int8_t {
        // Use means that the Inst will read from this value before doing anything else.
        //
        // For Tmp: The Inst will read this Tmp.
        // For Arg::addr and friends: The Inst will load from this address.
        // For Arg::imm and friends: The Inst will materialize and use this immediate.
        // For RelCond/ResCond/Special: This is the only valid role for these kinds.
        //
        // Note that Use of an address does not mean escape. It only means that the instruction will
        // load from the address before doing anything else. This is a bit tricky; for example
        // Specials could theoretically squirrel away the address and effectively escape it. However,
        // this is not legal. On the other hand, any address other than Stack is presumed to be
        // always escaping, and Stack is presumed to be always escaping if it's Locked.
        Use,

        // Exactly like Use, except that it also implies that the use is cold: that is, replacing the
        // use with something on the stack is free.
        ColdUse,

        // LateUse means that the Inst will read from this value after doing its Def's. Note that LateUse
        // on an Addr or Index still means Use on the internal temporaries. Note that specifying the
        // same Tmp once as Def and once as LateUse has undefined behavior: the use may happen before
        // the def, or it may happen after it.
        LateUse,

        // Combination of LateUse and ColdUse.
        LateColdUse,

        // Def means that the Inst will write to this value after doing everything else.
        //
        // For Tmp: The Inst will write to this Tmp.
        // For Arg::addr and friends: The Inst will store to this address.
        // This isn't valid for any other kinds.
        //
        // Like Use of address, Def of address does not mean escape.
        Def,

        // This is a special variant of Def that implies that the upper bits of the target register are
        // zero-filled. Specifically, if the Width of a ZDef is less than the largest possible width of
        // the argument (for example, we're on a 64-bit machine and we have a Width32 ZDef of a GPR) then
        // this has different implications for the upper bits (i.e. the top 32 bits in our example)
        // depending on the kind of the argument:
        //
        // For register: the upper bits are zero-filled.
        // For anonymous stack slot: the upper bits are zero-filled.
        // For address: the upper bits are not touched (i.e. we do a 32-bit store in our example).
        // For tmp: either the upper bits are not touched or they are zero-filled, and we won't know
        // which until we lower the tmp to either a StackSlot or a Reg.
        //
        // The behavior of ZDef is consistent with what happens when you perform 32-bit operations on a
        // 64-bit GPR. It's not consistent with what happens with 8-bit or 16-bit Defs on x86 GPRs, or
        // what happens with float Defs in ARM NEON or X86 SSE. Hence why we have both Def and ZDef.
        ZDef,

        // This is a combined Use and Def. It means that both things happen.
        UseDef,

        // This is a combined Use and ZDef. It means that both things happen.
        UseZDef,

        // This is like Def, but implies that the assignment occurs before the start of the Inst's
        // execution rather than after. Note that specifying the same Tmp once as EarlyDef and once
        // as Use has undefined behavior: the use may happen before the def, or it may happen after
        // it.
        EarlyDef,
            
        EarlyZDef,

        // Some instructions need a scratch register. We model this by saying that the temporary is
        // defined early and used late. This role implies that.
        Scratch,

        // This is a special kind of use that is only valid for addresses. It means that the
        // instruction will evaluate the address expression and consume the effective address, but it
        // will neither load nor store. This is an escaping use, because now the address may be
        // passed along to who-knows-where. Note that this isn't really a Use of the Arg, but it does
        // imply that we're Use'ing any registers that the Arg contains.
        UseAddr
    };

    enum Signedness : int8_t {
        Signed,
        Unsigned
    };

    // Returns true if the Role implies that the Inst will Use the Arg. It's deliberately false for
    // UseAddr, since isAnyUse() for an Arg::addr means that we are loading from the address.
    static bool isAnyUse(Role role)
    {
        switch (role) {
        case Use:
        case ColdUse:
        case UseDef:
        case UseZDef:
        case LateUse:
        case LateColdUse:
        case Scratch:
            return true;
        case Def:
        case ZDef:
        case UseAddr:
        case EarlyDef:
        case EarlyZDef:
            return false;
        }
        ASSERT_NOT_REACHED();
    }

    static bool isColdUse(Role role)
    {
        switch (role) {
        case ColdUse:
        case LateColdUse:
            return true;
        case Use:
        case UseDef:
        case UseZDef:
        case LateUse:
        case Def:
        case ZDef:
        case UseAddr:
        case Scratch:
        case EarlyDef:
        case EarlyZDef:
            return false;
        }
        ASSERT_NOT_REACHED();
    }

    static bool isWarmUse(Role role)
    {
        return isAnyUse(role) && !isColdUse(role);
    }

    static Role cooled(Role role)
    {
        switch (role) {
        case ColdUse:
        case LateColdUse:
        case UseDef:
        case UseZDef:
        case Def:
        case ZDef:
        case UseAddr:
        case Scratch:
        case EarlyDef:
        case EarlyZDef:
            return role;
        case Use:
            return ColdUse;
        case LateUse:
            return LateColdUse;
        }
        ASSERT_NOT_REACHED();
    }
    
    static Temperature temperature(Role role)
    {
        return isColdUse(role) ? Cold : Warm;
    }
    
    static bool activeAt(Role role, Phase phase)
    {
        switch (role) {
        case Use:
        case ColdUse:
        case EarlyDef:
        case EarlyZDef:
        case UseAddr:
            return phase == Early;
        case LateUse:
        case LateColdUse:
        case Def:
        case ZDef:
            return phase == Late;
        case UseDef:
        case UseZDef:
        case Scratch:
            return true;
        }
        ASSERT_NOT_REACHED();
    }
    
    static bool activeAt(Timing timing, Phase phase)
    {
        switch (timing) {
        case OnlyEarly:
            return phase == Early;
        case OnlyLate:
            return phase == Late;
        case EarlyAndLate:
            return true;
        }
        ASSERT_NOT_REACHED();
    }
    
    static Timing timing(Role role)
    {
        switch (role) {
        case Use:
        case ColdUse:
        case EarlyDef:
        case EarlyZDef:
        case UseAddr:
            return OnlyEarly;
        case LateUse:
        case LateColdUse:
        case Def:
        case ZDef:
            return OnlyLate;
        case UseDef:
        case UseZDef:
        case Scratch:
            return EarlyAndLate;
        }
        ASSERT_NOT_REACHED();
    }
    
    template<typename Func>
    static void forEachPhase(Timing timing, const Func& func)
    {
        if (activeAt(timing, Early))
            func(Early);
        if (activeAt(timing, Late))
            func(Late);
    }
    
    template<typename Func>
    static void forEachPhase(Role role, const Func& func)
    {
        if (activeAt(role, Early))
            func(Early);
        if (activeAt(role, Late))
            func(Late);
    }
    
    // Returns true if the Role implies that the Inst will Use the Arg before doing anything else.
    static bool isEarlyUse(Role role)
    {
        switch (role) {
        case Use:
        case ColdUse:
        case UseDef:
        case UseZDef:
            return true;
        case Def:
        case ZDef:
        case UseAddr:
        case LateUse:
        case LateColdUse:
        case Scratch:
        case EarlyDef:
        case EarlyZDef:
            return false;
        }
        ASSERT_NOT_REACHED();
    }

    // Returns true if the Role implies that the Inst will Use the Arg after doing everything else.
    static bool isLateUse(Role role)
    {
        switch (role) {
        case LateUse:
        case LateColdUse:
        case Scratch:
            return true;
        case ColdUse:
        case Use:
        case UseDef:
        case UseZDef:
        case Def:
        case ZDef:
        case UseAddr:
        case EarlyDef:
        case EarlyZDef:
            return false;
        }
        ASSERT_NOT_REACHED();
    }

    // Returns true if the Role implies that the Inst will Def the Arg.
    static bool isAnyDef(Role role)
    {
        switch (role) {
        case Use:
        case ColdUse:
        case UseAddr:
        case LateUse:
        case LateColdUse:
            return false;
        case Def:
        case UseDef:
        case ZDef:
        case UseZDef:
        case EarlyDef:
        case EarlyZDef:
        case Scratch:
            return true;
        }
        ASSERT_NOT_REACHED();
    }

    // Returns true if the Role implies that the Inst will Def the Arg before start of execution.
    static bool isEarlyDef(Role role)
    {
        switch (role) {
        case Use:
        case ColdUse:
        case UseAddr:
        case LateUse:
        case Def:
        case UseDef:
        case ZDef:
        case UseZDef:
        case LateColdUse:
            return false;
        case EarlyDef:
        case EarlyZDef:
        case Scratch:
            return true;
        }
        ASSERT_NOT_REACHED();
    }

    // Returns true if the Role implies that the Inst will Def the Arg after the end of execution.
    static bool isLateDef(Role role)
    {
        switch (role) {
        case Use:
        case ColdUse:
        case UseAddr:
        case LateUse:
        case EarlyDef:
        case EarlyZDef:
        case Scratch:
        case LateColdUse:
            return false;
        case Def:
        case UseDef:
        case ZDef:
        case UseZDef:
            return true;
        }
        ASSERT_NOT_REACHED();
    }

    // Returns true if the Role implies that the Inst will ZDef the Arg.
    static bool isZDef(Role role)
    {
        switch (role) {
        case Use:
        case ColdUse:
        case UseAddr:
        case LateUse:
        case Def:
        case UseDef:
        case EarlyDef:
        case Scratch:
        case LateColdUse:
            return false;
        case ZDef:
        case UseZDef:
        case EarlyZDef:
            return true;
        }
        ASSERT_NOT_REACHED();
    }

    Arg()
        : m_kind(Invalid)
    {
    }

    Arg(Air::Tmp tmp)
        : m_kind(Tmp)
        , m_base(tmp)
    {
    }

    Arg(Reg reg)
        : Arg(Air::Tmp(reg))
    {
    }

    static Arg simdInfo(SIMDLane simdLane, SIMDSignMode signMode = SIMDSignMode::None)
    {
        Arg result;
        result.m_kind = SIMDInfo;
        result.m_simdInfo = ::JSC::SIMDInfo { simdLane, signMode };
        return result;
    }

    static Arg simdInfo(::JSC::SIMDInfo info)
    {
        Arg result;
        result.m_kind = SIMDInfo;
        result.m_simdInfo = info;
        return result;
    }

    static Arg imm(int64_t value)
    {
        if constexpr (is32Bit())
            RELEASE_ASSERT((Fits<int64_t, Wide32>::check(value) || Fits<uint64_t, Wide32>::check(value)));
        Arg result;
        result.m_kind = Imm;
        result.m_offset = value;
        return result;
    }

    static Arg bigImm(int64_t value)
    {
        if constexpr (is32Bit())
            RELEASE_ASSERT((Fits<int64_t, Wide32>::check(value) || Fits<uint64_t, Wide32>::check(value)));
        Arg result;
        result.m_kind = BigImm;
        result.m_offset = value;
        return result;
    }

    static Arg bigImmLo32(int64_t value)
    {
        return bigImm(value & 0xffffffff);
    }

    static Arg bigImmHi32(int64_t value)
    {
        return bigImm(value >> 32);
    }

    static Arg bitImm(int64_t value)
    {
        if constexpr (is32Bit())
            RELEASE_ASSERT((Fits<int64_t, Wide32>::check(value)));
        Arg result;
        result.m_kind = BitImm;
        result.m_offset = value;
        return result;
    }

    static Arg bitImm64(int64_t value)
    {
        if constexpr (is32Bit())
            UNREACHABLE_FOR_PLATFORM();
        Arg result;
        result.m_kind = BitImm64;
        result.m_offset = value;
        return result;
    }

    static Arg fpImm32(int64_t value)
    {
        if constexpr (is32Bit())
            RELEASE_ASSERT((Fits<int64_t, Wide32>::check(value)));
        Arg result;
        result.m_kind = FPImm32;
        result.m_offset = value;
        return result;
    }

    static Arg fpImm64(int64_t value)
    {
        if constexpr (is32Bit())
            UNREACHABLE_FOR_PLATFORM();
        Arg result;
        result.m_kind = FPImm64;
        result.m_offset = value;
        return result;
    }

    static Arg fpImm128(v128_t value)
    {
        if constexpr (is32Bit())
            UNREACHABLE_FOR_PLATFORM();
        Arg result;
        result.m_kind = FPImm128;
        result.m_offset = value.u64x2[0];
        result.m_additional = value.u64x2[0];
        return result;
    }

    static Arg immPtr(const void* address)
    {
        return bigImm(std::bit_cast<intptr_t>(address));
    }

    static Arg simpleAddr(Air::Tmp ptr)
    {
        ASSERT(ptr.isGP());
        Arg result;
        result.m_kind = SimpleAddr;
        result.m_base = ptr;
        return result;
    }

    template<IsLegalOffset Int>
    static Arg addr(Air::Tmp base, Int offset)
    {
        ASSERT(base.isGP());
        Arg result;
        result.m_kind = Addr;
        result.m_base = base;
        result.m_offset = offset;
        return result;
    }

    template<IsLegalOffset Int>
    static Arg extendedOffsetAddr(Int offsetFromFP)
    {
        Arg result;
        result.m_kind = ExtendedOffsetAddr;
        result.m_base = Air::Tmp(MacroAssembler::framePointerRegister);
        result.m_offset = offsetFromFP;
        return result;
    }

    static Arg addr(Air::Tmp base)
    {
        return addr(base, 0);
    }

    template<IsLegalOffset Int>
    static Arg stack(StackSlot* value, Int offset)
    {
        Arg result;
        result.m_kind = Stack;
        result.m_offset = std::bit_cast<intptr_t>(value);
        result.m_scale = offset; // I know, yuck.
        return result;
    }

    static Arg stack(StackSlot* value)
    {
        return stack(value, 0);
    }

    template<IsLegalOffset Int>
    static Arg callArg(Int offset)
    {
        Arg result;
        result.m_kind = CallArg;
        result.m_offset = offset;
        return result;
    }

    // If you don't pass a Width, this optimistically assumes that you're using the right width.
    static bool isValidScale(unsigned scale, std::optional<Width> width = std::nullopt)
    {
        switch (scale) {
        case 1:
            if (isX86() || isARM64() || isARM_THUMB2())
                return true;
            return false;
        case 2:
        case 4:
        case 8:
            if (isX86() || isARM_THUMB2())
                return true;
            if (isARM64()) {
                if (!width)
                    return true;
                return scale == 1 || scale == bytesForWidth(*width);
            }
            return false;
        default:
            return false;
        }
    }

    static unsigned logScale(unsigned scale)
    {
        switch (scale) {
        case 1:
            return 0;
        case 2:
            return 1;
        case 4:
            return 2;
        case 8:
            return 3;
        case 16:
            return 4;
        default:
            ASSERT_NOT_REACHED();
            return 0;
        }
    }

    template<IsLegalOffset Int>
    static Arg index(Air::Tmp base, Air::Tmp index, unsigned scale, Int offset, MacroAssembler::Extend extend = MacroAssembler::Extend::None)
    {
        ASSERT(base.isGP());
        ASSERT(index.isGP());
        ASSERT(isValidScale(scale));
        Arg result;
        result.m_kind = Index;
        result.m_base = base;
        result.m_index = index;
        result.m_scale = static_cast<int32_t>(scale);
        result.m_offset = offset;
        result.m_extend = extend;
        return result;
    }

    static Arg index(Air::Tmp base, Air::Tmp index, unsigned scale = 1)
    {
        return Arg::index(base, index, scale, 0);
    }

    template<IsLegalOffset Int>
    static Arg preIndex(Air::Tmp base, Int index)
    {
        ASSERT(base.isGP());
        ASSERT(isInt9(index));
        Arg result;
        result.m_kind = PreIndex;
        result.m_base = base;
        result.m_offset = index;
        return result;
    }

    template<IsLegalOffset Int>
    static Arg postIndex(Air::Tmp base, Int index)
    {
        ASSERT(base.isGP());
        ASSERT(isInt9(index));
        Arg result;
        result.m_kind = PostIndex;
        result.m_base = base;
        result.m_offset = index;
        return result;
    }

    static Arg relCond(MacroAssembler::RelationalCondition condition)
    {
        Arg result;
        result.m_kind = RelCond;
        result.m_offset = condition;
        return result;
    }

    static Arg resCond(MacroAssembler::ResultCondition condition)
    {
        Arg result;
        result.m_kind = ResCond;
        result.m_offset = condition;
        return result;
    }

    static Arg doubleCond(MacroAssembler::DoubleCondition condition)
    {
        Arg result;
        result.m_kind = DoubleCond;
        result.m_offset = condition;
        return result;
    }

    static Arg statusCond(MacroAssembler::StatusCondition condition)
    {
        Arg result;
        result.m_kind = StatusCond;
        result.m_offset = condition;
        return result;
    }

    static Arg special(Air::Special* special)
    {
        Arg result;
        result.m_kind = Special;
        result.m_offset = std::bit_cast<intptr_t>(special);
        return result;
    }

    static Arg widthArg(Width width)
    {
        Arg result;
        result.m_kind = WidthArg;
        result.m_offset = bytesForWidth(width);
        return result;
    }

    static Arg zeroReg()
    {
        Arg result;
        result.m_kind = ZeroReg;
        result.m_offset = 0;
        return result;
    }

    bool operator==(const Arg& other) const
    {
        return m_offset == other.m_offset
            && m_kind == other.m_kind
            && m_base == other.m_base
            && m_index == other.m_index
            && m_scale == other.m_scale;
    }

    explicit operator bool() const { return *this != Arg(); }

    Kind kind() const
    {
        return m_kind;
    }

    bool isTmp() const
    {
        return kind() == Tmp;
    }

    bool isImm() const
    {
        return kind() == Imm;
    }

    bool isBigImm() const
    {
        return kind() == BigImm;
    }

    bool isBitImm() const
    {
        return kind() == BitImm;
    }

    bool isBitImm64() const
    {
        return kind() == BitImm64;
    }

    bool isFPImm32() const
    {
        return kind() == FPImm32;
    }

    bool isFPImm64() const
    {
        return kind() == FPImm64;
    }

    bool isFPImm128() const
    {
        return kind() == FPImm128;
    }

    bool isZeroReg() const
    {
        return kind() == ZeroReg;
    }

    bool isSomeImm() const
    {
        switch (kind()) {
        case Imm:
        case BigImm:
        case BitImm:
        case BitImm64:
        case FPImm32:
        case FPImm64:
        case FPImm128:
            return true;
        default:
            return false;
        }
    }

    bool isSimpleAddr() const
    {
        return kind() == SimpleAddr;
    }

    bool isAddr() const
    {
        return kind() == Addr;
    }

    bool isExtendedOffsetAddr() const
    {
        return kind() == ExtendedOffsetAddr;
    }

    bool isStack() const
    {
        return kind() == Stack;
    }

    bool isCallArg() const
    {
        return kind() == CallArg;
    }

    bool isIndex() const
    {
        return kind() == Index;
    }

    bool isPreIndex() const
    {
        return kind() == PreIndex;
    }

    bool isPostIndex() const
    {
        return kind() == PostIndex;
    }

    bool isMemory() const
    {
        switch (kind()) {
        case SimpleAddr:
        case Addr:
        case ExtendedOffsetAddr:
        case Stack:
        case CallArg:
        case Index:
        case PreIndex:
        case PostIndex:
            return true;
        default:
            return false;
        }
    }

    // Returns true if this is an idiomatic stack reference. It may return false for some kinds of
    // stack references. The following idioms are recognized:
    // - the Stack kind
    // - the CallArg kind
    // - the ExtendedOffsetAddr kind
    // - the Addr kind with the base being either SP or FP
    // Callers of this function are allowed to expect that if it returns true, then it must be one of
    // these easy-to-recognize kinds. So, making this function recognize more kinds could break things.
    bool isStackMemory() const;

    bool isRelCond() const
    {
        return kind() == RelCond;
    }

    bool isResCond() const
    {
        return kind() == ResCond;
    }

    bool isDoubleCond() const
    {
        return kind() == DoubleCond;
    }

    bool isStatusCond() const
    {
        return kind() == StatusCond;
    }

    bool isCondition() const
    {
        switch (kind()) {
        case RelCond:
        case ResCond:
        case DoubleCond:
        case StatusCond:
            return true;
        default:
            return false;
        }
    }

    bool isSpecial() const
    {
        return kind() == Special;
    }

    bool isWidthArg() const
    {
        return kind() == WidthArg;
    }

    bool isAlive() const
    {
        return isTmp() || isStack();
    }
    
    bool isSIMDInfo() const
    {
        return kind() == SIMDInfo;
    }

    Air::Tmp tmp() const
    {
        ASSERT(kind() == Tmp);
        return m_base;
    }

    int64_t value() const
    {
        ASSERT(isSomeImm());
        return m_offset;
    }

    template<typename T>
    bool isRepresentableAs() const
    {
        return WTF::isRepresentableAs<T>(value());
    }
    
    static bool isRepresentableAs(Width width, Signedness signedness, int64_t value)
    {
        switch (signedness) {
        case Signed:
            switch (width) {
            case Width8:
                return WTF::isRepresentableAs<int8_t>(value);
            case Width16:
                return WTF::isRepresentableAs<int16_t>(value);
            case Width32:
                return WTF::isRepresentableAs<int32_t>(value);
            case Width64:
                return WTF::isRepresentableAs<int64_t>(value);
            case Width128:
                break;
            }
            RELEASE_ASSERT_NOT_REACHED();
        case Unsigned:
            switch (width) {
            case Width8:
                return WTF::isRepresentableAs<uint8_t>(value);
            case Width16:
                return WTF::isRepresentableAs<uint16_t>(value);
            case Width32:
                return WTF::isRepresentableAs<uint32_t>(value);
            case Width64:
                return WTF::isRepresentableAs<uint64_t>(value);
            case Width128:
                break;
            }
        }
        RELEASE_ASSERT_NOT_REACHED();
    }

    bool isRepresentableAs(Width, Signedness) const;
    
    static int64_t castToType(Width width, Signedness signedness, int64_t value)
    {
        switch (signedness) {
        case Signed:
            switch (width) {
            case Width8:
                return static_cast<int8_t>(value);
            case Width16:
                return static_cast<int16_t>(value);
            case Width32:
                return static_cast<int32_t>(value);
            case Width64:
                return static_cast<int64_t>(value);
            case Width128:
                break;
            }
            RELEASE_ASSERT_NOT_REACHED();
        case Unsigned:
            switch (width) {
            case Width8:
                return static_cast<uint8_t>(value);
            case Width16:
                return static_cast<uint16_t>(value);
            case Width32:
                return static_cast<uint32_t>(value);
            case Width64:
                return static_cast<uint64_t>(value);
            case Width128:
                break;
            }
        }
        RELEASE_ASSERT_NOT_REACHED();
    }

    template<typename T>
    T asNumber() const
    {
        return static_cast<T>(value());
    }

    void* pointerValue() const
    {
        ASSERT(kind() == BigImm);
        return std::bit_cast<void*>(static_cast<intptr_t>(m_offset));
    }
    
    Air::Tmp ptr() const
    {
        ASSERT(kind() == SimpleAddr);
        return m_base;
    }

    Air::Tmp base() const
    {
        ASSERT(kind() == SimpleAddr || kind() == Addr || kind() == ExtendedOffsetAddr || kind() == Index || kind() == PreIndex || kind() == PostIndex);
        return m_base;
    }

    bool hasOffset() const { return isMemory(); }
    
    Value::OffsetType offset() const
    {
        if (kind() == Stack)
            return static_cast<Value::OffsetType>(m_scale);
        ASSERT(kind() == Addr || kind() == ExtendedOffsetAddr || kind() == CallArg || kind() == Index || kind() == PreIndex || kind() == PostIndex);
        return static_cast<Value::OffsetType>(m_offset);
    }

    StackSlot* stackSlot() const
    {
        ASSERT(kind() == Stack);
        return std::bit_cast<StackSlot*>(static_cast<uintptr_t>(m_offset));
    }

    Air::Tmp index() const
    {
        ASSERT(kind() == Index);
        return m_index;
    }

    unsigned scale() const
    {
        ASSERT(kind() == Index);
        return m_scale;
    }

    unsigned logScale() const
    {
        return logScale(scale());
    }

    MacroAssembler::Extend extend() const
    {
        return m_extend;
    }

    Air::Special* special() const
    {
        ASSERT(kind() == Special);
        return std::bit_cast<Air::Special*>(static_cast<uintptr_t>(m_offset));
    }

    Width width() const
    {
        ASSERT(kind() == WidthArg);
        return widthForBytes(m_offset);
    }

    bool isGPTmp() const
    {
        return isTmp() && tmp().isGP();
    }

    bool isFPTmp() const
    {
        return isTmp() && tmp().isFP();
    }
    
    // Tells us if this Arg can be used in a position that requires a GP value.
    bool isGP() const
    {
        switch (kind()) {
        case Imm:
        case BigImm:
        case BitImm:
        case BitImm64:
        case FPImm32:
        case FPImm64:
        case FPImm128:
        case ZeroReg:
        case SimpleAddr:
        case Addr:
        case ExtendedOffsetAddr:
        case Index:
        case PreIndex:
        case PostIndex:
        case Stack:
        case CallArg:
        case RelCond:
        case ResCond:
        case DoubleCond:
        case StatusCond:
        case Special:
        case WidthArg:
            return true;
        case Tmp:
            return isGPTmp();
        case SIMDInfo:
        case Invalid:
            return false;
        }
        ASSERT_NOT_REACHED();
    }

    // Tells us if this Arg can be used in a position that requires a FP value.
    bool isFP() const
    {
        switch (kind()) {
        case Imm:
        case BitImm:
        case BitImm64:
        case FPImm32:
        case FPImm64:
        case FPImm128:
        case RelCond:
        case ResCond:
        case DoubleCond:
        case StatusCond:
        case Special:
        case WidthArg:
        case Invalid:
        case ZeroReg:
        case SIMDInfo:
            return false;
        case SimpleAddr:
        case Addr:
        case ExtendedOffsetAddr:
        case Index:
        case PreIndex:
        case PostIndex:
        case Stack:
        case CallArg:
        case BigImm: // Yes, we allow BigImm as a double immediate. We use this for implementing stackmaps.
            return true;
        case Tmp:
            return isFPTmp();
        }
        ASSERT_NOT_REACHED();
    }

    bool hasBank() const
    {
        switch (kind()) {
        case Imm:
        case BitImm:
        case BitImm64:
        case Special:
        case Tmp:
            return true;
        default:
            return false;
        }
    }
    
    // The type is ambiguous for some arg kinds. Call with care.
    Bank bank() const
    {
        return isGP() ? GP : FP;
    }

    bool isBank(Bank bank) const
    {
        switch (bank) {
        case GP:
            return isGP();
        case FP:
            return isFP();
        }
        ASSERT_NOT_REACHED();
    }

    bool canRepresent(Type) const;
    bool canRepresent(Value* value) const;

    bool isCompatibleBank(const Arg& other) const;

    bool isGPR() const
    {
        return isTmp() && tmp().isGPR();
    }

    GPRReg gpr() const
    {
        return tmp().gpr();
    }

    bool isFPR() const
    {
        return isTmp() && tmp().isFPR();
    }

    FPRReg fpr() const
    {
        return tmp().fpr();
    }
    
    bool isReg() const
    {
        return isTmp() && tmp().isReg();
    }

    Reg reg() const
    {
        return tmp().reg();
    }

    unsigned gpTmpIndex() const
    {
        return tmp().gpTmpIndex();
    }

    unsigned fpTmpIndex() const
    {
        return tmp().fpTmpIndex();
    }

    unsigned tmpIndex() const
    {
        return tmp().tmpIndex();
    }

    static bool isValidImmForm(int64_t value)
    {
        if (isX86())
            return WTF::isRepresentableAs<int32_t>(value);
        if (isARM64()) {
            if (isUInt12(value) || isUInt12(toTwosComplement(value)))
                return true;
            int64_t shifted = value >> 12;
            if ((shifted << 12) == value)
                return isUInt12(shifted) || isUInt12(toTwosComplement(shifted));
            return false;
        }
        if (isARM_THUMB2())
            return isValidARMThumb2Immediate(value);
        return false;
    }

    static bool isValidBitImmForm(int64_t value)
    {
        if (isX86())
            return WTF::isRepresentableAs<int32_t>(value);
        if (isARM64())
            return ARM64LogicalImmediate::create32(value).isValid();
        if (isARM_THUMB2())
            return isValidARMThumb2Immediate(value);
        return false;
    }

    static bool isValidBitImm64Form(int64_t value)
    {
        if (isX86())
            return WTF::isRepresentableAs<int32_t>(value);
        if (isARM64())
            return ARM64LogicalImmediate::create64(value).isValid();
        return false;
    }

    static bool isValidFPImm16Form(int64_t value)
    {
        if (!value)
            return true;

        if (!isARM64())
            return false;

#if CPU(ARM64)
        if (MacroAssembler::supportsFloat16() && ARM64Assembler::canEncodeFPImm<16>(value))
            return true;

        uint16_t u16 = static_cast<uint16_t>(value);

        auto shiftedImm = ARM64ShiftedImmediate16::create(u16);
        if (shiftedImm.isValid())
            return true;

        auto shiftedImmInverted = ARM64ShiftedImmediate16::create(~u16);
        if (shiftedImmInverted.isValid())
            return true;

        uint64_t u64 = u16;
        if (ARM64FPImmediate::create64(u64).isValid())
            return true;

        uint8_t low8 = static_cast<uint8_t>(u16);
        uint8_t high8 = static_cast<uint8_t>(u16 >> 8);
        if (low8 == high8)
            return true;
#endif

        return false;
    }

    static bool isValidFPImm32Form(int64_t value)
    {
        if (!value)
            return true;

        if (!isARM64())
            return false;

#if CPU(ARM64)
        if (ARM64Assembler::canEncodeFPImm<32>(value))
            return true;

        uint32_t u32 = static_cast<uint32_t>(value);

        auto shiftedImm = ARM64ShiftedImmediate32::create(u32);
        if (shiftedImm.isValid())
            return true;

        auto shiftedImmInverted = ARM64ShiftedImmediate32::create(~u32);
        if (shiftedImmInverted.isValid())
            return true;

        auto mslImm = ARM64ShiftedImmediateMSL32::create(u32);
        if (mslImm.isValid())
            return true;

        auto mslImmInverted = ARM64ShiftedImmediateMSL32::create(~u32);
        if (mslImmInverted.isValid())
            return true;

        uint64_t u64 = u32;
        if (ARM64FPImmediate::create64(u64).isValid())
            return true;

        uint16_t low16 = static_cast<uint16_t>(u32);
        uint16_t high16 = static_cast<uint16_t>(u32 >> 16);
        if (low16 == high16)
            return isValidFPImm16Form(static_cast<int16_t>(low16));
#endif

        return false;
    }

    static bool isValidFPImm64Form(int64_t value)
    {
        if (!value)
            return true;

        if (!isARM64())
            return false;

#if CPU(ARM64)
        if (ARM64Assembler::canEncodeFPImm<64>(value))
            return true;

        uint64_t u64 = static_cast<uint64_t>(value);
        if (ARM64FPImmediate::create64(u64).isValid())
            return true;

        uint32_t low32 = static_cast<uint32_t>(u64);
        uint32_t high32 = static_cast<uint32_t>(u64 >> 32);
        if (low32 == high32)
            return isValidFPImm32Form(static_cast<int32_t>(low32));
#endif

        return false;
    }

    static bool isValidFPImm128Form(v128_t value)
    {
        if (bitEquals(value, vectorAllZeros()))
            return true;

        if (!isARM64())
            return false;

#if CPU(ARM64)
        WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
        if (value.u64x2[0] == value.u64x2[1])
            return isValidFPImm64Form(value.u64x2[0]);
        WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
#endif

        return false;
    }

    template<IsLegalOffset Int>
    static bool isValidAddrForm(Air::Opcode opcode, Int offset, std::optional<Width> width = std::nullopt)
    {
#if !CPU(ARM_THUMB2)
        UNUSED_PARAM(opcode);
#endif
        if (isX86())
            return true;

        if (!width)
            return true;

        if (isARM64()) {
            if (isValidSignedImm9(offset))
                return true;

            switch (*width) {
            case Width8:
                return isValidScaledUImm12<8>(offset);
            case Width16:
                return isValidScaledUImm12<16>(offset);
            case Width32:
                return isValidScaledUImm12<32>(offset);
            case Width64:
                return isValidScaledUImm12<64>(offset);
            case Width128:
                return isValidScaledUImm12<128>(offset);
            }
        }

#if CPU(ARM_THUMB2)
        switch (opcode) {
        case Move:
        case Move32:
            return MacroAssemblerARMv7::BoundsNonDoubleWordOffset::within(offset);
        case MoveDouble:
        case MoveFloat:
            if constexpr (!std::is_signed_v<Int>)
                return !((offset & 3) || (offset > (255 * 4)));
            else
                return !((offset & 3) || (offset > (255 * 4)) || (static_cast<typename std::make_signed<Int>::type>(offset) < -(255 * 4)));
        default:
            return false;
        }
#endif
        return false;
    }

    template<IsLegalOffset Int>
    static bool isValidIndexForm(Air::Opcode opcode, unsigned scale, Int offset, std::optional<Width> width = std::nullopt)
    {
        if (!isValidScale(scale, width))
            return false;
        if (isX86())
            return true;
        if (isARM64())
            return !offset;
        if (isARM_THUMB2()) {
            switch (opcode) {
            case MoveFloat:
            case MoveDouble:
                return false;
            default:
                return !offset;
            }
        }
        return false;
    }

    template<IsLegalOffset Int>
    static bool isValidIncrementIndexForm(Int offset)
    {
        if (isARM64())
            return isValidSignedImm9(offset);
        return false;
    }

    // If you don't pass a width then this optimistically assumes that you're using the right width. But
    // the width is relevant to validity, so passing a null width is only useful for assertions. Don't
    // pass null widths when cascading through Args in the instruction selector!
    bool isValidForm(Air::Opcode opcode, std::optional<Width> width = std::nullopt) const
    {
        switch (kind()) {
        case Invalid:
            return false;
        case Tmp:
            return true;
        case Imm:
            return isValidImmForm(value());
        case BigImm:
            return true;
        case BitImm:
            return isValidBitImmForm(value());
        case BitImm64:
            return isValidBitImm64Form(value());
        case FPImm32:
            return isValidFPImm32Form(value());
        case FPImm64:
            return isValidFPImm64Form(value());
        case FPImm128:
            return isValidFPImm128Form(asV128());
        case ZeroReg:
        case SimpleAddr:
        case ExtendedOffsetAddr:
        case SIMDInfo:
            return true;
        case Addr:
        case Stack:
        case CallArg:
            return isValidAddrForm(opcode, offset(), width);
        case Index:
            return isValidIndexForm(opcode, scale(), offset(), width);
        case PreIndex:
        case PostIndex:
            return isValidIncrementIndexForm(offset());
        case RelCond:
        case ResCond:
        case DoubleCond:
        case StatusCond:
        case Special:
        case WidthArg:
            return true;
        }
        ASSERT_NOT_REACHED();
    }

    template<typename Functor>
    void forEachTmpFast(const Functor& functor)
    {
        switch (m_kind) {
        case Tmp:
        case SimpleAddr:
        case Addr:
        case ExtendedOffsetAddr:
        case PreIndex:
        case PostIndex:
            functor(m_base);
            break;
        case Index:
            functor(m_base);
            functor(m_index);
            break;
        default:
            break;
        }
    }

    bool usesTmp(Air::Tmp tmp) const;

    template<typename Thing>
    bool is() const;

    template<typename Thing>
    Thing as() const;

    template<typename Thing, typename Functor>
    void forEachFast(const Functor&);

    template<typename Thing, typename Functor>
    void forEach(Role, Bank, Width, const Functor&);

    // This is smart enough to know that an address arg in a Def or UseDef rule will use its
    // tmps and never def them. For example, this:
    //
    // mov %rax, (%rcx)
    //
    // This defs (%rcx) but uses %rcx.
    template<typename Functor>
    void forEachTmp(Role argRole, Bank argBank, Width argWidth, const Functor& functor)
    {
        switch (m_kind) {
        case Tmp:
            ASSERT(isAnyUse(argRole) || isAnyDef(argRole));
            functor(m_base, argRole, argBank, argWidth);
            break;
        case SimpleAddr:
        case Addr:
        case ExtendedOffsetAddr:
            functor(m_base, Use, GP, argRole == UseAddr ? argWidth : pointerWidth());
            break;
        case PreIndex:
        case PostIndex:
            functor(m_base, UseDef, GP, argRole == UseAddr ? argWidth : pointerWidth());
            break;
        case Index:
            functor(m_base, Use, GP, argRole == UseAddr ? argWidth : pointerWidth());
            functor(m_index, Use, GP, argRole == UseAddr ? argWidth : pointerWidth());
            break;
        default:
            break;
        }
    }

    MacroAssembler::TrustedImm32 asTrustedImm32() const
    {
        ASSERT(isImm() || isBitImm() || isFPImm32());
        return MacroAssembler::TrustedImm32(static_cast<Value::OffsetType>(m_offset));
    }

    MacroAssembler::TrustedImm64 asTrustedImm64() const
    {
        if constexpr (is32Bit())
            UNREACHABLE_FOR_PLATFORM();
        ASSERT(isBigImm() || isBitImm64() || isFPImm64());
        return MacroAssembler::TrustedImm64(value());
    }

    v128_t asV128() const
    {
        if constexpr (is32Bit())
            UNREACHABLE_FOR_PLATFORM();
        ASSERT(isFPImm128());
        return v128_t(m_offset, m_additional);
    }

    decltype(auto) asTrustedBigImm() const
    {
        ASSERT(isBigImm());
        if constexpr (is32Bit())
            return MacroAssembler::TrustedImm32(value());
        else
            return MacroAssembler::TrustedImm64(value());
    }

#if CPU(ARM64)
    MacroAssembler::RegisterID asZeroReg() const
    {
        return ARM64Registers::zr;
    }
#endif

    MacroAssembler::TrustedImmPtr asTrustedImmPtr() const
    {
        if (is64Bit())
            ASSERT(isBigImm());
        else
            ASSERT(isImm());
        return MacroAssembler::TrustedImmPtr(pointerValue());
    }
    
    MacroAssembler::Address asAddress() const
    {
        if (isSimpleAddr())
            return MacroAssembler::Address(m_base.gpr());
        ASSERT(isAddr() || isExtendedOffsetAddr());
        return MacroAssembler::Address(m_base.gpr(), static_cast<Value::OffsetType>(m_offset));
    }

    MacroAssembler::BaseIndex asBaseIndex() const
    {
        ASSERT(isIndex());
        return MacroAssembler::BaseIndex(
            m_base.gpr(), m_index.gpr(), static_cast<MacroAssembler::Scale>(logScale()),
            static_cast<Value::OffsetType>(m_offset), m_extend);
    }

    MacroAssembler::PreIndexAddress asPreIndexAddress() const
    {
        ASSERT(isPreIndex());
        return MacroAssembler::PreIndexAddress(m_base.gpr(), offset());
    }

    MacroAssembler::PostIndexAddress asPostIndexAddress() const
    {
        ASSERT(isPostIndex());
        return MacroAssembler::PostIndexAddress(m_base.gpr(), offset());
    }

    MacroAssembler::RelationalCondition asRelationalCondition() const
    {
        ASSERT(isRelCond());
        return static_cast<MacroAssembler::RelationalCondition>(m_offset);
    }

    MacroAssembler::ResultCondition asResultCondition() const
    {
        ASSERT(isResCond());
        return static_cast<MacroAssembler::ResultCondition>(m_offset);
    }

    MacroAssembler::DoubleCondition asDoubleCondition() const
    {
        ASSERT(isDoubleCond());
        return static_cast<MacroAssembler::DoubleCondition>(m_offset);
    }
    
    MacroAssembler::StatusCondition asStatusCondition() const
    {
        ASSERT(isStatusCond());
        return static_cast<MacroAssembler::StatusCondition>(m_offset);
    }
    
    ::JSC::SIMDInfo simdInfo() const
    {
        ASSERT(isSIMDInfo());
        return m_simdInfo;
    }
    
    SIMDSignMode simdSignMode() const
    {
        ASSERT(isSIMDInfo());
        return m_simdInfo.signMode;
    }
    
    // Tells you if the Arg is invertible. Only condition arguments are invertible, and even for those, there
    // are a few exceptions - notably Overflow and Signed.
    bool isInvertible() const
    {
        switch (kind()) {
        case RelCond:
        case DoubleCond:
        case StatusCond:
            return true;
        case ResCond:
            return MacroAssembler::isInvertible(asResultCondition());
        default:
            return false;
        }
    }

    // This is valid for condition arguments. It will invert them.
    Arg inverted(bool inverted = true) const
    {
        if (!inverted)
            return *this;
        switch (kind()) {
        case RelCond:
            return relCond(MacroAssembler::invert(asRelationalCondition()));
        case ResCond:
            return resCond(MacroAssembler::invert(asResultCondition()));
        case DoubleCond:
            return doubleCond(MacroAssembler::invert(asDoubleCondition()));
        case StatusCond:
            return statusCond(MacroAssembler::invert(asStatusCondition()));
        default:
            RELEASE_ASSERT_NOT_REACHED();
            return Arg();
        }
    }

    Arg flipped(bool flipped = true) const
    {
        if (!flipped)
            return Arg();
        return relCond(MacroAssembler::flip(asRelationalCondition()));
    }

    bool isSignedCond() const
    {
        return isRelCond() && MacroAssembler::isSigned(asRelationalCondition());
    }

    bool isUnsignedCond() const
    {
        return isRelCond() && MacroAssembler::isUnsigned(asRelationalCondition());
    }

    // This computes a hash for comparing this to JSAir's Arg.
    unsigned jsHash() const;
    
    void dump(PrintStream&) const;

    Arg(WTF::HashTableDeletedValueType)
        : m_base(WTF::HashTableDeletedValue)
    {
    }

    bool isHashTableDeletedValue() const
    {
        return *this == Arg(WTF::HashTableDeletedValue);
    }

    static constexpr bool safeToCompareToHashTableEmptyOrDeletedValue = true;

    unsigned hash() const
    {
        // This really doesn't have to be that great.
        return WTF::IntHash<int64_t>::hash(m_offset) + m_kind + m_scale + m_base.hash() +
            m_index.hash();
    }

private:
    int64_t m_offset { 0 };
    int64_t m_additional { 0 };
    Kind m_kind { Invalid };
    MacroAssembler::Extend m_extend { MacroAssembler::Extend::None };
    JSC::SIMDInfo m_simdInfo;
    int32_t m_scale { 1 };
    Air::Tmp m_base;
    Air::Tmp m_index;
#if USE(JSVALUE32_64)
    // XXX: stick in union with m_base?
    Air::Tmp m_baseHi;
    Air::Tmp m_baseLo;
#endif
};

} } } // namespace JSC::B3::Air

namespace WTF {

JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Kind);
JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Temperature);
JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Phase);
JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Timing);
JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Role);
JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Signedness);

template<typename T> struct HashTraits;
template<> struct HashTraits<JSC::B3::Air::Arg> : SimpleClassHashTraits<JSC::B3::Air::Arg> {
    // Because m_scale is 1 in the empty value.
    static constexpr bool emptyValueIsZero = false;
};

} // namespace WTF

#if !ASSERT_ENABLED
IGNORE_RETURN_TYPE_WARNINGS_END
#endif

#endif // ENABLE(B3_JIT)
