diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/b3/air/AirArg.h | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/JavaScriptCore/b3/air/AirArg.h')
-rw-r--r-- | Source/JavaScriptCore/b3/air/AirArg.h | 1383 |
1 files changed, 1383 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/b3/air/AirArg.h b/Source/JavaScriptCore/b3/air/AirArg.h new file mode 100644 index 000000000..13db1ce7e --- /dev/null +++ b/Source/JavaScriptCore/b3/air/AirArg.h @@ -0,0 +1,1383 @@ +/* + * Copyright (C) 2015-2016 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 "AirTmp.h" +#include "B3Common.h" +#include "B3Type.h" +#include <wtf/Optional.h> + +#if COMPILER(GCC) && ASSERT_DISABLED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type" +#endif // COMPILER(GCC) && ASSERT_DISABLED + +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, + + // These are the addresses. Instructions may load from (Use), store to (Def), or evaluate + // (UseAddr) addresses. + Addr, + Stack, + CallArg, + Index, + + // 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, + Special, + WidthArg + }; + + 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, + + // 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 Type : int8_t { + GP, + FP + }; + + static const unsigned numTypes = 2; + + template<typename Functor> + static void forEachType(const Functor& functor) + { + functor(GP); + functor(FP); + } + + enum Width : int8_t { + Width8, + Width16, + Width32, + Width64 + }; + + static Width pointerWidth() + { + if (sizeof(void*) == 8) + return Width64; + return Width32; + } + + 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: + 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: + 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: + return role; + case Use: + return ColdUse; + case LateUse: + return LateColdUse; + } + ASSERT_NOT_REACHED(); + } + + // 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: + 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: + 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 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 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 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: + return true; + } + ASSERT_NOT_REACHED(); + } + + static Type typeForB3Type(B3::Type type) + { + switch (type) { + case Void: + ASSERT_NOT_REACHED(); + return GP; + case Int32: + case Int64: + return GP; + case Float: + case Double: + return FP; + } + ASSERT_NOT_REACHED(); + return GP; + } + + static Width widthForB3Type(B3::Type type) + { + switch (type) { + case Void: + ASSERT_NOT_REACHED(); + return Width8; + case Int32: + case Float: + return Width32; + case Int64: + case Double: + return Width64; + } + ASSERT_NOT_REACHED(); + } + + static Width conservativeWidth(Type type) + { + return type == GP ? pointerWidth() : Width64; + } + + static Width minimumWidth(Type type) + { + return type == GP ? Width8 : Width32; + } + + static unsigned bytes(Width width) + { + return 1 << width; + } + + static Width widthForBytes(unsigned bytes) + { + switch (bytes) { + case 0: + case 1: + return Width8; + case 2: + return Width16; + case 3: + case 4: + return Width32; + default: + return Width64; + } + } + + Arg() + : m_kind(Invalid) + { + } + + Arg(Air::Tmp tmp) + : m_kind(Tmp) + , m_base(tmp) + { + } + + Arg(Reg reg) + : Arg(Air::Tmp(reg)) + { + } + + static Arg imm(int64_t value) + { + Arg result; + result.m_kind = Imm; + result.m_offset = value; + return result; + } + + static Arg bigImm(int64_t value) + { + Arg result; + result.m_kind = BigImm; + result.m_offset = value; + return result; + } + + static Arg bitImm(int64_t value) + { + Arg result; + result.m_kind = BitImm; + result.m_offset = value; + return result; + } + + static Arg bitImm64(int64_t value) + { + Arg result; + result.m_kind = BitImm64; + result.m_offset = value; + return result; + } + + static Arg immPtr(const void* address) + { + return bigImm(bitwise_cast<intptr_t>(address)); + } + + static Arg addr(Air::Tmp base, int32_t offset = 0) + { + ASSERT(base.isGP()); + Arg result; + result.m_kind = Addr; + result.m_base = base; + result.m_offset = offset; + return result; + } + + static Arg stack(StackSlot* value, int32_t offset = 0) + { + Arg result; + result.m_kind = Stack; + result.m_offset = bitwise_cast<intptr_t>(value); + result.m_scale = offset; // I know, yuck. + return result; + } + + static Arg callArg(int32_t offset) + { + Arg result; + result.m_kind = CallArg; + result.m_offset = offset; + return result; + } + + static Arg stackAddr(int32_t offsetFromFP, unsigned frameSize, Width width) + { + Arg result = Arg::addr(Air::Tmp(GPRInfo::callFrameRegister), offsetFromFP); + if (!result.isValidForm(width)) { + result = Arg::addr( + Air::Tmp(MacroAssembler::stackPointerRegister), + offsetFromFP + frameSize); + } + 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()) + return true; + return false; + case 2: + case 4: + case 8: + if (isX86()) + return true; + if (isARM64()) { + if (!width) + return true; + return scale == 1 || scale == bytes(*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; + default: + ASSERT_NOT_REACHED(); + return 0; + } + } + + static Arg index(Air::Tmp base, Air::Tmp index, unsigned scale = 1, int32_t offset = 0) + { + 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; + 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 special(Air::Special* special) + { + Arg result; + result.m_kind = Special; + result.m_offset = bitwise_cast<intptr_t>(special); + return result; + } + + static Arg widthArg(Width width) + { + Arg result; + result.m_kind = WidthArg; + result.m_offset = width; + 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; + } + + bool operator!=(const Arg& other) const + { + return !(*this == other); + } + + 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 isSomeImm() const + { + switch (kind()) { + case Imm: + case BigImm: + case BitImm: + case BitImm64: + return true; + default: + return false; + } + } + + bool isAddr() const + { + return kind() == Addr; + } + + bool isStack() const + { + return kind() == Stack; + } + + bool isCallArg() const + { + return kind() == CallArg; + } + + bool isIndex() const + { + return kind() == Index; + } + + bool isMemory() const + { + switch (kind()) { + case Addr: + case Stack: + case CallArg: + case Index: + return true; + default: + return false; + } + } + + bool isStackMemory() const; + + bool isRelCond() const + { + return kind() == RelCond; + } + + bool isResCond() const + { + return kind() == ResCond; + } + + bool isDoubleCond() const + { + return kind() == DoubleCond; + } + + bool isCondition() const + { + switch (kind()) { + case RelCond: + case ResCond: + case DoubleCond: + return true; + default: + return false; + } + } + + bool isSpecial() const + { + return kind() == Special; + } + + bool isWidthArg() const + { + return kind() == WidthArg; + } + + bool isAlive() const + { + return isTmp() || isStack(); + } + + 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 B3::isRepresentableAs<T>(value()); + } + + static bool isRepresentableAs(Width width, Signedness signedness, int64_t value) + { + switch (signedness) { + case Signed: + switch (width) { + case Width8: + return B3::isRepresentableAs<int8_t>(value); + case Width16: + return B3::isRepresentableAs<int16_t>(value); + case Width32: + return B3::isRepresentableAs<int32_t>(value); + case Width64: + return B3::isRepresentableAs<int64_t>(value); + } + case Unsigned: + switch (width) { + case Width8: + return B3::isRepresentableAs<uint8_t>(value); + case Width16: + return B3::isRepresentableAs<uint16_t>(value); + case Width32: + return B3::isRepresentableAs<uint32_t>(value); + case Width64: + return B3::isRepresentableAs<uint64_t>(value); + } + } + 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 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); + } + } + ASSERT_NOT_REACHED(); + } + + template<typename T> + T asNumber() const + { + return static_cast<T>(value()); + } + + void* pointerValue() const + { + ASSERT(kind() == BigImm); + return bitwise_cast<void*>(static_cast<intptr_t>(m_offset)); + } + + Air::Tmp base() const + { + ASSERT(kind() == Addr || kind() == Index); + return m_base; + } + + bool hasOffset() const { return isMemory(); } + + int32_t offset() const + { + if (kind() == Stack) + return static_cast<int32_t>(m_scale); + ASSERT(kind() == Addr || kind() == CallArg || kind() == Index); + return static_cast<int32_t>(m_offset); + } + + StackSlot* stackSlot() const + { + ASSERT(kind() == Stack); + return bitwise_cast<StackSlot*>(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()); + } + + Air::Special* special() const + { + ASSERT(kind() == Special); + return bitwise_cast<Air::Special*>(m_offset); + } + + Width width() const + { + ASSERT(kind() == WidthArg); + return static_cast<Width>(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 Addr: + case Index: + case Stack: + case CallArg: + case RelCond: + case ResCond: + case DoubleCond: + case Special: + case WidthArg: + return true; + case Tmp: + return isGPTmp(); + 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 RelCond: + case ResCond: + case DoubleCond: + case Special: + case WidthArg: + case Invalid: + return false; + case Addr: + case Index: + 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 hasType() 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. + Type type() const + { + return isGP() ? GP : FP; + } + + bool isType(Type type) const + { + switch (type) { + case GP: + return isGP(); + case FP: + return isFP(); + } + ASSERT_NOT_REACHED(); + } + + bool canRepresent(Value* value) const; + + bool isCompatibleType(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 B3::isRepresentableAs<int32_t>(value); + if (isARM64()) + return isUInt12(value); + return false; + } + + static bool isValidBitImmForm(int64_t value) + { + if (isX86()) + return B3::isRepresentableAs<int32_t>(value); + if (isARM64()) + return ARM64LogicalImmediate::create32(value).isValid(); + return false; + } + + static bool isValidBitImm64Form(int64_t value) + { + if (isX86()) + return B3::isRepresentableAs<int32_t>(value); + if (isARM64()) + return ARM64LogicalImmediate::create64(value).isValid(); + return false; + } + + static bool isValidAddrForm(int32_t offset, std::optional<Width> width = std::nullopt) + { + if (isX86()) + return true; + if (isARM64()) { + if (!width) + return true; + + 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); + } + } + return false; + } + + static bool isValidIndexForm(unsigned scale, int32_t offset, std::optional<Width> width = std::nullopt) + { + if (!isValidScale(scale, width)) + return false; + if (isX86()) + return true; + if (isARM64()) + return !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(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 Addr: + case Stack: + case CallArg: + return isValidAddrForm(offset(), width); + case Index: + return isValidIndexForm(scale(), offset(), width); + case RelCond: + case ResCond: + case DoubleCond: + case Special: + case WidthArg: + return true; + } + ASSERT_NOT_REACHED(); + } + + template<typename Functor> + void forEachTmpFast(const Functor& functor) + { + switch (m_kind) { + case Tmp: + case Addr: + 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, Type, 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, Type argType, Width argWidth, const Functor& functor) + { + switch (m_kind) { + case Tmp: + ASSERT(isAnyUse(argRole) || isAnyDef(argRole)); + functor(m_base, argRole, argType, argWidth); + break; + case Addr: + functor(m_base, Use, 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()); + return MacroAssembler::TrustedImm32(static_cast<int32_t>(m_offset)); + } + +#if USE(JSVALUE64) + MacroAssembler::TrustedImm64 asTrustedImm64() const + { + ASSERT(isBigImm() || isBitImm64()); + return MacroAssembler::TrustedImm64(value()); + } +#endif + + MacroAssembler::TrustedImmPtr asTrustedImmPtr() const + { + if (is64Bit()) + ASSERT(isBigImm()); + else + ASSERT(isImm()); + return MacroAssembler::TrustedImmPtr(pointerValue()); + } + + MacroAssembler::Address asAddress() const + { + ASSERT(isAddr()); + return MacroAssembler::Address(m_base.gpr(), static_cast<int32_t>(m_offset)); + } + + MacroAssembler::BaseIndex asBaseIndex() const + { + ASSERT(isIndex()); + return MacroAssembler::BaseIndex( + m_base.gpr(), m_index.gpr(), static_cast<MacroAssembler::Scale>(logScale()), + static_cast<int32_t>(m_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); + } + + // 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: + 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())); + 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); + } + + 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 }; + Kind m_kind { Invalid }; + int32_t m_scale { 1 }; + Air::Tmp m_base; + Air::Tmp m_index; +}; + +struct ArgHash { + static unsigned hash(const Arg& key) { return key.hash(); } + static bool equal(const Arg& a, const Arg& b) { return a == b; } + static const bool safeToCompareToEmptyOrDeleted = true; +}; + +} } } // 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::Role); +JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Type); +JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Width); +JS_EXPORT_PRIVATE void printInternal(PrintStream&, JSC::B3::Air::Arg::Signedness); + +template<typename T> struct DefaultHash; +template<> struct DefaultHash<JSC::B3::Air::Arg> { + typedef JSC::B3::Air::ArgHash Hash; +}; + +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 const bool emptyValueIsZero = false; +}; + +} // namespace WTF + +#if COMPILER(GCC) && ASSERT_DISABLED +#pragma GCC diagnostic pop +#endif // COMPILER(GCC) && ASSERT_DISABLED + +#endif // ENABLE(B3_JIT) |