| /* |
| * Copyright (C) 2011-2018 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(DFG_JIT) |
| |
| #include "B3SparseCollection.h" |
| #include "BasicBlockLocation.h" |
| #include "CodeBlock.h" |
| #include "DFGAdjacencyList.h" |
| #include "DFGArithMode.h" |
| #include "DFGArrayMode.h" |
| #include "DFGCommon.h" |
| #include "DFGEpoch.h" |
| #include "DFGLazyJSValue.h" |
| #include "DFGMultiGetByOffsetData.h" |
| #include "DFGNodeFlags.h" |
| #include "DFGNodeOrigin.h" |
| #include "DFGNodeType.h" |
| #include "DFGObjectMaterializationData.h" |
| #include "DFGOpInfo.h" |
| #include "DFGRegisteredStructure.h" |
| #include "DFGRegisteredStructureSet.h" |
| #include "DFGTransition.h" |
| #include "DFGUseKind.h" |
| #include "DFGVariableAccessData.h" |
| #include "GetByIdVariant.h" |
| #include "JSCJSValue.h" |
| #include "Operands.h" |
| #include "PutByIdVariant.h" |
| #include "SpeculatedType.h" |
| #include "TypeLocation.h" |
| #include "ValueProfile.h" |
| #include <type_traits> |
| #include <wtf/ListDump.h> |
| #include <wtf/LoggingHashSet.h> |
| |
| namespace JSC { |
| |
| namespace DOMJIT { |
| class GetterSetter; |
| class CallDOMGetterSnippet; |
| class Signature; |
| } |
| |
| namespace Profiler { |
| class ExecutionCounter; |
| } |
| |
| class Snippet; |
| |
| namespace DFG { |
| |
| class Graph; |
| class PromotedLocationDescriptor; |
| struct BasicBlock; |
| |
| struct StorageAccessData { |
| PropertyOffset offset; |
| unsigned identifierNumber; |
| }; |
| |
| struct MultiPutByOffsetData { |
| unsigned identifierNumber; |
| Vector<PutByIdVariant, 2> variants; |
| |
| bool writesStructures() const; |
| bool reallocatesStorage() const; |
| }; |
| |
| struct MatchStructureVariant { |
| RegisteredStructure structure; |
| bool result; |
| }; |
| |
| struct MatchStructureData { |
| Vector<MatchStructureVariant, 2> variants; |
| }; |
| |
| struct NewArrayBufferData { |
| union { |
| struct { |
| unsigned vectorLengthHint; |
| unsigned indexingMode; |
| }; |
| uint64_t asQuadWord; |
| }; |
| }; |
| static_assert(sizeof(IndexingType) <= sizeof(unsigned), ""); |
| static_assert(sizeof(NewArrayBufferData) == sizeof(uint64_t), ""); |
| |
| struct DataViewData { |
| union { |
| struct { |
| uint8_t byteSize; |
| bool isSigned; |
| bool isFloatingPoint; // Used for the DataViewSet node. |
| TriState isLittleEndian; |
| }; |
| uint64_t asQuadWord; |
| }; |
| }; |
| static_assert(sizeof(DataViewData) == sizeof(uint64_t), ""); |
| |
| struct BranchTarget { |
| BranchTarget() |
| : block(0) |
| , count(PNaN) |
| { |
| } |
| |
| explicit BranchTarget(BasicBlock* block) |
| : block(block) |
| , count(PNaN) |
| { |
| } |
| |
| void setBytecodeIndex(unsigned bytecodeIndex) |
| { |
| block = bitwise_cast<BasicBlock*>(static_cast<uintptr_t>(bytecodeIndex)); |
| } |
| unsigned bytecodeIndex() const { return bitwise_cast<uintptr_t>(block); } |
| |
| void dump(PrintStream&) const; |
| |
| BasicBlock* block; |
| float count; |
| }; |
| |
| struct BranchData { |
| static BranchData withBytecodeIndices( |
| unsigned takenBytecodeIndex, unsigned notTakenBytecodeIndex) |
| { |
| BranchData result; |
| result.taken.block = bitwise_cast<BasicBlock*>(static_cast<uintptr_t>(takenBytecodeIndex)); |
| result.notTaken.block = bitwise_cast<BasicBlock*>(static_cast<uintptr_t>(notTakenBytecodeIndex)); |
| return result; |
| } |
| |
| unsigned takenBytecodeIndex() const { return taken.bytecodeIndex(); } |
| unsigned notTakenBytecodeIndex() const { return notTaken.bytecodeIndex(); } |
| |
| BasicBlock*& forCondition(bool condition) |
| { |
| if (condition) |
| return taken.block; |
| return notTaken.block; |
| } |
| |
| BranchTarget taken; |
| BranchTarget notTaken; |
| }; |
| |
| // The SwitchData and associated data structures duplicate the information in |
| // JumpTable. The DFG may ultimately end up using the JumpTable, though it may |
| // instead decide to do something different - this is entirely up to the DFG. |
| // These data structures give the DFG a higher-level semantic description of |
| // what is going on, which will allow it to make the right decision. |
| // |
| // Note that there will never be multiple SwitchCases in SwitchData::cases that |
| // have the same SwitchCase::value, since the bytecode's JumpTables never have |
| // duplicates - since the JumpTable maps a value to a target. It's a |
| // one-to-many mapping. So we may have duplicate targets, but never duplicate |
| // values. |
| struct SwitchCase { |
| SwitchCase() |
| { |
| } |
| |
| SwitchCase(LazyJSValue value, BasicBlock* target) |
| : value(value) |
| , target(target) |
| { |
| } |
| |
| static SwitchCase withBytecodeIndex(LazyJSValue value, unsigned bytecodeIndex) |
| { |
| SwitchCase result; |
| result.value = value; |
| result.target.setBytecodeIndex(bytecodeIndex); |
| return result; |
| } |
| |
| LazyJSValue value; |
| BranchTarget target; |
| }; |
| |
| struct SwitchData { |
| // Initializes most fields to obviously invalid values. Anyone |
| // constructing this should make sure to initialize everything they |
| // care about manually. |
| SwitchData() |
| : switchTableIndex(UINT_MAX) |
| , kind(static_cast<SwitchKind>(-1)) |
| , didUseJumpTable(false) |
| { |
| } |
| |
| Vector<SwitchCase> cases; |
| BranchTarget fallThrough; |
| size_t switchTableIndex; |
| SwitchKind kind; |
| bool didUseJumpTable; |
| }; |
| |
| struct EntrySwitchData { |
| Vector<BasicBlock*> cases; |
| }; |
| |
| struct CallVarargsData { |
| int firstVarArgOffset; |
| }; |
| |
| struct LoadVarargsData { |
| VirtualRegister start; // Local for the first element. This is the first actual argument, not this. |
| VirtualRegister count; // Local for the count. |
| VirtualRegister machineStart; |
| VirtualRegister machineCount; |
| unsigned offset; // Which array element to start with. Usually this is 0. |
| unsigned mandatoryMinimum; // The number of elements on the stack that must be initialized; if the array is too short then the missing elements must get undefined. Does not include "this". |
| unsigned limit; // Maximum number of elements to load. Includes "this". |
| }; |
| |
| struct StackAccessData { |
| StackAccessData() |
| : format(DeadFlush) |
| { |
| } |
| |
| StackAccessData(VirtualRegister local, FlushFormat format) |
| : local(local) |
| , format(format) |
| { |
| } |
| |
| VirtualRegister local; |
| VirtualRegister machineLocal; |
| FlushFormat format; |
| |
| FlushedAt flushedAt() { return FlushedAt(format, machineLocal); } |
| }; |
| |
| struct CallDOMGetterData { |
| FunctionPtr<OperationPtrTag> customAccessorGetter; |
| const DOMJIT::GetterSetter* domJIT { nullptr }; |
| DOMJIT::CallDOMGetterSnippet* snippet { nullptr }; |
| unsigned identifierNumber { 0 }; |
| }; |
| |
| enum class BucketOwnerType : uint32_t { |
| Map, |
| Set |
| }; |
| |
| // === Node === |
| // |
| // Node represents a single operation in the data flow graph. |
| struct Node { |
| WTF_MAKE_FAST_ALLOCATED; |
| public: |
| static const char HashSetTemplateInstantiationString[]; |
| |
| enum VarArgTag { VarArg }; |
| |
| Node() { } |
| |
| Node(NodeType op, NodeOrigin nodeOrigin, const AdjacencyList& children) |
| : origin(nodeOrigin) |
| , children(children) |
| , m_virtualRegister(VirtualRegister()) |
| , m_refCount(1) |
| , m_prediction(SpecNone) |
| , owner(nullptr) |
| { |
| m_misc.replacement = nullptr; |
| setOpAndDefaultFlags(op); |
| } |
| |
| // Construct a node with up to 3 children, no immediate value. |
| Node(NodeType op, NodeOrigin nodeOrigin, Edge child1 = Edge(), Edge child2 = Edge(), Edge child3 = Edge()) |
| : origin(nodeOrigin) |
| , children(AdjacencyList::Fixed, child1, child2, child3) |
| , m_virtualRegister(VirtualRegister()) |
| , m_refCount(1) |
| , m_prediction(SpecNone) |
| , owner(nullptr) |
| { |
| m_misc.replacement = nullptr; |
| setOpAndDefaultFlags(op); |
| ASSERT(!(m_flags & NodeHasVarArgs)); |
| } |
| |
| // Construct a node with up to 3 children, no immediate value. |
| Node(NodeFlags result, NodeType op, NodeOrigin nodeOrigin, Edge child1 = Edge(), Edge child2 = Edge(), Edge child3 = Edge()) |
| : origin(nodeOrigin) |
| , children(AdjacencyList::Fixed, child1, child2, child3) |
| , m_virtualRegister(VirtualRegister()) |
| , m_refCount(1) |
| , m_prediction(SpecNone) |
| , owner(nullptr) |
| { |
| m_misc.replacement = nullptr; |
| setOpAndDefaultFlags(op); |
| setResult(result); |
| ASSERT(!(m_flags & NodeHasVarArgs)); |
| } |
| |
| // Construct a node with up to 3 children and an immediate value. |
| Node(NodeType op, NodeOrigin nodeOrigin, OpInfo imm, Edge child1 = Edge(), Edge child2 = Edge(), Edge child3 = Edge()) |
| : origin(nodeOrigin) |
| , children(AdjacencyList::Fixed, child1, child2, child3) |
| , m_virtualRegister(VirtualRegister()) |
| , m_refCount(1) |
| , m_prediction(SpecNone) |
| , m_opInfo(imm.m_value) |
| , owner(nullptr) |
| { |
| m_misc.replacement = nullptr; |
| setOpAndDefaultFlags(op); |
| ASSERT(!(m_flags & NodeHasVarArgs)); |
| } |
| |
| // Construct a node with up to 3 children and an immediate value. |
| Node(NodeFlags result, NodeType op, NodeOrigin nodeOrigin, OpInfo imm, Edge child1 = Edge(), Edge child2 = Edge(), Edge child3 = Edge()) |
| : origin(nodeOrigin) |
| , children(AdjacencyList::Fixed, child1, child2, child3) |
| , m_virtualRegister(VirtualRegister()) |
| , m_refCount(1) |
| , m_prediction(SpecNone) |
| , m_opInfo(imm.m_value) |
| , owner(nullptr) |
| { |
| m_misc.replacement = nullptr; |
| setOpAndDefaultFlags(op); |
| setResult(result); |
| ASSERT(!(m_flags & NodeHasVarArgs)); |
| } |
| |
| // Construct a node with up to 3 children and two immediate values. |
| Node(NodeType op, NodeOrigin nodeOrigin, OpInfo imm1, OpInfo imm2, Edge child1 = Edge(), Edge child2 = Edge(), Edge child3 = Edge()) |
| : origin(nodeOrigin) |
| , children(AdjacencyList::Fixed, child1, child2, child3) |
| , m_virtualRegister(VirtualRegister()) |
| , m_refCount(1) |
| , m_prediction(SpecNone) |
| , m_opInfo(imm1.m_value) |
| , m_opInfo2(imm2.m_value) |
| , owner(nullptr) |
| { |
| m_misc.replacement = nullptr; |
| setOpAndDefaultFlags(op); |
| ASSERT(!(m_flags & NodeHasVarArgs)); |
| } |
| |
| // Construct a node with a variable number of children and two immediate values. |
| Node(VarArgTag, NodeType op, NodeOrigin nodeOrigin, OpInfo imm1, OpInfo imm2, unsigned firstChild, unsigned numChildren) |
| : origin(nodeOrigin) |
| , children(AdjacencyList::Variable, firstChild, numChildren) |
| , m_virtualRegister(VirtualRegister()) |
| , m_refCount(1) |
| , m_prediction(SpecNone) |
| , m_opInfo(imm1.m_value) |
| , m_opInfo2(imm2.m_value) |
| , owner(nullptr) |
| { |
| m_misc.replacement = nullptr; |
| setOpAndDefaultFlags(op); |
| ASSERT(m_flags & NodeHasVarArgs); |
| } |
| |
| NodeType op() const { return static_cast<NodeType>(m_op); } |
| NodeFlags flags() const { return m_flags; } |
| |
| unsigned index() const { return m_index; } |
| |
| void setOp(NodeType op) |
| { |
| m_op = op; |
| } |
| |
| void setFlags(NodeFlags flags) |
| { |
| m_flags = flags; |
| } |
| |
| bool mergeFlags(NodeFlags flags) |
| { |
| NodeFlags newFlags = m_flags | flags; |
| if (newFlags == m_flags) |
| return false; |
| m_flags = newFlags; |
| return true; |
| } |
| |
| bool filterFlags(NodeFlags flags) |
| { |
| NodeFlags newFlags = m_flags & flags; |
| if (newFlags == m_flags) |
| return false; |
| m_flags = newFlags; |
| return true; |
| } |
| |
| bool clearFlags(NodeFlags flags) |
| { |
| return filterFlags(~flags); |
| } |
| |
| void setResult(NodeFlags result) |
| { |
| ASSERT(!(result & ~NodeResultMask)); |
| clearFlags(NodeResultMask); |
| mergeFlags(result); |
| } |
| |
| NodeFlags result() const |
| { |
| return flags() & NodeResultMask; |
| } |
| |
| void setOpAndDefaultFlags(NodeType op) |
| { |
| m_op = op; |
| m_flags = defaultFlags(op); |
| } |
| |
| void remove(Graph&); |
| void removeWithoutChecks(); |
| |
| void convertToCheckStructure(RegisteredStructureSet* set) |
| { |
| setOpAndDefaultFlags(CheckStructure); |
| m_opInfo = set; |
| } |
| |
| void convertToCheckStructureOrEmpty(RegisteredStructureSet* set) |
| { |
| if (SpecCellCheck & SpecEmpty) |
| setOpAndDefaultFlags(CheckStructureOrEmpty); |
| else |
| setOpAndDefaultFlags(CheckStructure); |
| m_opInfo = set; |
| } |
| |
| void convertCheckStructureOrEmptyToCheckStructure() |
| { |
| ASSERT(op() == CheckStructureOrEmpty); |
| setOpAndDefaultFlags(CheckStructure); |
| } |
| |
| void convertToCheckStructureImmediate(Node* structure) |
| { |
| ASSERT(op() == CheckStructure || op() == CheckStructureOrEmpty); |
| m_op = CheckStructureImmediate; |
| children.setChild1(Edge(structure, CellUse)); |
| } |
| |
| void replaceWith(Graph&, Node* other); |
| void replaceWithWithoutChecks(Node* other); |
| |
| void convertToIdentity(); |
| void convertToIdentityOn(Node*); |
| |
| bool mustGenerate() |
| { |
| return m_flags & NodeMustGenerate; |
| } |
| |
| bool isConstant() |
| { |
| switch (op()) { |
| case JSConstant: |
| case DoubleConstant: |
| case Int52Constant: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool hasConstant() |
| { |
| switch (op()) { |
| case JSConstant: |
| case DoubleConstant: |
| case Int52Constant: |
| return true; |
| |
| case PhantomDirectArguments: |
| case PhantomClonedArguments: |
| // These pretend to be the empty value constant for the benefit of the DFG backend, which |
| // otherwise wouldn't take kindly to a node that doesn't compute a value. |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| FrozenValue* constant() |
| { |
| ASSERT(hasConstant()); |
| |
| if (op() == PhantomDirectArguments || op() == PhantomClonedArguments) { |
| // These pretend to be the empty value constant for the benefit of the DFG backend, which |
| // otherwise wouldn't take kindly to a node that doesn't compute a value. |
| return FrozenValue::emptySingleton(); |
| } |
| |
| return m_opInfo.as<FrozenValue*>(); |
| } |
| |
| // Don't call this directly - use Graph::convertToConstant() instead! |
| void convertToConstant(FrozenValue* value) |
| { |
| if (hasDoubleResult()) |
| m_op = DoubleConstant; |
| else if (hasInt52Result()) |
| m_op = Int52Constant; |
| else |
| m_op = JSConstant; |
| m_flags &= ~(NodeMustGenerate | NodeHasVarArgs); |
| m_opInfo = value; |
| children.reset(); |
| } |
| |
| void convertToLazyJSConstant(Graph&, LazyJSValue); |
| |
| void convertToConstantStoragePointer(void* pointer) |
| { |
| ASSERT(op() == GetIndexedPropertyStorage); |
| m_op = ConstantStoragePointer; |
| m_opInfo = pointer; |
| children.reset(); |
| } |
| |
| void convertToPutStack(StackAccessData* data) |
| { |
| m_op = PutStack; |
| m_flags |= NodeMustGenerate; |
| m_opInfo = data; |
| m_opInfo2 = OpInfoWrapper(); |
| } |
| |
| void convertToGetStack(StackAccessData* data) |
| { |
| m_op = GetStack; |
| m_flags &= ~NodeMustGenerate; |
| m_opInfo = data; |
| m_opInfo2 = OpInfoWrapper(); |
| children.reset(); |
| } |
| |
| void convertToGetByOffset(StorageAccessData& data, Edge storage, Edge base) |
| { |
| ASSERT(m_op == GetById || m_op == GetByIdFlush || m_op == GetByIdDirect || m_op == GetByIdDirectFlush || m_op == MultiGetByOffset); |
| m_opInfo = &data; |
| children.setChild1(storage); |
| children.setChild2(base); |
| m_op = GetByOffset; |
| m_flags &= ~NodeMustGenerate; |
| } |
| |
| void convertToMultiGetByOffset(MultiGetByOffsetData* data) |
| { |
| RELEASE_ASSERT(m_op == GetById || m_op == GetByIdFlush || m_op == GetByIdDirect || m_op == GetByIdDirectFlush); |
| m_opInfo = data; |
| child1().setUseKind(CellUse); |
| m_op = MultiGetByOffset; |
| RELEASE_ASSERT(m_flags & NodeMustGenerate); |
| } |
| |
| void convertToPutByOffset(StorageAccessData& data, Edge storage, Edge base) |
| { |
| ASSERT(m_op == PutById || m_op == PutByIdDirect || m_op == PutByIdFlush || m_op == MultiPutByOffset); |
| m_opInfo = &data; |
| children.setChild3(children.child2()); |
| children.setChild2(base); |
| children.setChild1(storage); |
| m_op = PutByOffset; |
| } |
| |
| void convertToMultiPutByOffset(MultiPutByOffsetData* data) |
| { |
| ASSERT(m_op == PutById || m_op == PutByIdDirect || m_op == PutByIdFlush); |
| m_opInfo = data; |
| m_op = MultiPutByOffset; |
| } |
| |
| void convertToPhantomNewObject() |
| { |
| ASSERT(m_op == NewObject || m_op == MaterializeNewObject); |
| m_op = PhantomNewObject; |
| m_flags &= ~NodeHasVarArgs; |
| m_flags |= NodeMustGenerate; |
| m_opInfo = OpInfoWrapper(); |
| m_opInfo2 = OpInfoWrapper(); |
| children = AdjacencyList(); |
| } |
| |
| void convertToPhantomNewFunction() |
| { |
| ASSERT(m_op == NewFunction || m_op == NewGeneratorFunction || m_op == NewAsyncFunction || m_op == NewAsyncGeneratorFunction); |
| m_op = PhantomNewFunction; |
| m_flags |= NodeMustGenerate; |
| m_opInfo = OpInfoWrapper(); |
| m_opInfo2 = OpInfoWrapper(); |
| children = AdjacencyList(); |
| } |
| |
| void convertToPhantomNewGeneratorFunction() |
| { |
| ASSERT(m_op == NewGeneratorFunction); |
| m_op = PhantomNewGeneratorFunction; |
| m_flags |= NodeMustGenerate; |
| m_opInfo = OpInfoWrapper(); |
| m_opInfo2 = OpInfoWrapper(); |
| children = AdjacencyList(); |
| } |
| |
| void convertToPhantomNewAsyncFunction() |
| { |
| ASSERT(m_op == NewAsyncFunction); |
| m_op = PhantomNewAsyncFunction; |
| m_flags |= NodeMustGenerate; |
| m_opInfo = OpInfoWrapper(); |
| m_opInfo2 = OpInfoWrapper(); |
| children = AdjacencyList(); |
| } |
| |
| void convertToPhantomNewAsyncGeneratorFunction() |
| { |
| ASSERT(m_op == NewAsyncGeneratorFunction); |
| m_op = PhantomNewAsyncGeneratorFunction; |
| m_flags |= NodeMustGenerate; |
| m_opInfo = OpInfoWrapper(); |
| m_opInfo2 = OpInfoWrapper(); |
| children = AdjacencyList(); |
| } |
| |
| void convertToPhantomCreateActivation() |
| { |
| ASSERT(m_op == CreateActivation || m_op == MaterializeCreateActivation); |
| m_op = PhantomCreateActivation; |
| m_flags &= ~NodeHasVarArgs; |
| m_flags |= NodeMustGenerate; |
| m_opInfo = OpInfoWrapper(); |
| m_opInfo2 = OpInfoWrapper(); |
| children = AdjacencyList(); |
| } |
| |
| void convertToPhantomNewRegexp() |
| { |
| ASSERT(m_op == NewRegexp); |
| setOpAndDefaultFlags(PhantomNewRegexp); |
| m_opInfo = OpInfoWrapper(); |
| m_opInfo2 = OpInfoWrapper(); |
| children = AdjacencyList(); |
| } |
| |
| void convertPhantomToPhantomLocal() |
| { |
| ASSERT(m_op == Phantom && (child1()->op() == Phi || child1()->op() == SetLocal || child1()->op() == SetArgumentDefinitely)); |
| m_op = PhantomLocal; |
| m_opInfo = child1()->m_opInfo; // Copy the variableAccessData. |
| children.setChild1(Edge()); |
| } |
| |
| void convertFlushToPhantomLocal() |
| { |
| ASSERT(m_op == Flush); |
| m_op = PhantomLocal; |
| children = AdjacencyList(); |
| } |
| |
| void convertToToString() |
| { |
| ASSERT(m_op == ToPrimitive || m_op == StringValueOf); |
| m_op = ToString; |
| } |
| |
| void convertToArithNegate() |
| { |
| ASSERT(m_op == ArithAbs && child1().useKind() == Int32Use); |
| m_op = ArithNegate; |
| } |
| |
| void convertToCompareEqPtr(FrozenValue* cell, Edge node) |
| { |
| ASSERT(m_op == CompareStrictEq || m_op == SameValue); |
| setOpAndDefaultFlags(CompareEqPtr); |
| children.setChild1(node); |
| children.setChild2(Edge()); |
| m_opInfo = cell; |
| } |
| |
| void convertToNumberToStringWithValidRadixConstant(int32_t radix) |
| { |
| ASSERT(m_op == NumberToStringWithRadix); |
| ASSERT(2 <= radix && radix <= 36); |
| setOpAndDefaultFlags(NumberToStringWithValidRadixConstant); |
| children.setChild2(Edge()); |
| m_opInfo = radix; |
| } |
| |
| void convertToGetGlobalThis() |
| { |
| ASSERT(m_op == ToThis); |
| setOpAndDefaultFlags(GetGlobalThis); |
| children.setChild1(Edge()); |
| } |
| |
| void convertToCallObjectConstructor(FrozenValue* globalObject) |
| { |
| ASSERT(m_op == ToObject); |
| setOpAndDefaultFlags(CallObjectConstructor); |
| m_opInfo = globalObject; |
| } |
| |
| void convertToNewStringObject(RegisteredStructure structure) |
| { |
| ASSERT(m_op == CallObjectConstructor || m_op == ToObject); |
| setOpAndDefaultFlags(NewStringObject); |
| m_opInfo = structure; |
| m_opInfo2 = OpInfoWrapper(); |
| } |
| |
| void convertToNewObject(RegisteredStructure structure) |
| { |
| ASSERT(m_op == CallObjectConstructor || m_op == CreateThis || m_op == ObjectCreate); |
| setOpAndDefaultFlags(NewObject); |
| children.reset(); |
| m_opInfo = structure; |
| m_opInfo2 = OpInfoWrapper(); |
| } |
| |
| void convertToNewPromise(RegisteredStructure structure) |
| { |
| ASSERT(m_op == CreatePromise); |
| bool internal = isInternalPromise(); |
| setOpAndDefaultFlags(NewPromise); |
| children.reset(); |
| m_opInfo = structure; |
| m_opInfo2 = internal; |
| } |
| |
| void convertToNewInternalFieldObject(NodeType newOp, RegisteredStructure structure) |
| { |
| ASSERT(m_op == CreateAsyncGenerator || m_op == CreateGenerator); |
| setOpAndDefaultFlags(newOp); |
| children.reset(); |
| m_opInfo = structure; |
| m_opInfo2 = OpInfoWrapper(); |
| } |
| |
| void convertToNewArrayBuffer(FrozenValue* immutableButterfly); |
| |
| void convertToDirectCall(FrozenValue*); |
| |
| void convertToCallDOM(Graph&); |
| |
| void convertToRegExpExecNonGlobalOrStickyWithoutChecks(FrozenValue* regExp); |
| void convertToRegExpMatchFastGlobalWithoutChecks(FrozenValue* regExp); |
| |
| void convertToSetRegExpObjectLastIndex() |
| { |
| setOp(SetRegExpObjectLastIndex); |
| m_opInfo = false; |
| } |
| |
| void convertToInById(unsigned identifierNumber) |
| { |
| ASSERT(m_op == InByVal); |
| setOpAndDefaultFlags(InById); |
| children.setChild2(Edge()); |
| m_opInfo = identifierNumber; |
| m_opInfo2 = OpInfoWrapper(); |
| } |
| |
| JSValue asJSValue() |
| { |
| return constant()->value(); |
| } |
| |
| bool isInt32Constant() |
| { |
| return isConstant() && constant()->value().isInt32(); |
| } |
| |
| int32_t asInt32() |
| { |
| return asJSValue().asInt32(); |
| } |
| |
| uint32_t asUInt32() |
| { |
| return asInt32(); |
| } |
| |
| bool isDoubleConstant() |
| { |
| return isConstant() && constant()->value().isDouble(); |
| } |
| |
| bool isNumberConstant() |
| { |
| return isConstant() && constant()->value().isNumber(); |
| } |
| |
| double asNumber() |
| { |
| return asJSValue().asNumber(); |
| } |
| |
| bool isAnyIntConstant() |
| { |
| return isConstant() && constant()->value().isAnyInt(); |
| } |
| |
| int64_t asAnyInt() |
| { |
| return asJSValue().asAnyInt(); |
| } |
| |
| bool isBooleanConstant() |
| { |
| return isConstant() && constant()->value().isBoolean(); |
| } |
| |
| bool asBoolean() |
| { |
| return constant()->value().asBoolean(); |
| } |
| |
| bool isUndefinedOrNullConstant() |
| { |
| return isConstant() && constant()->value().isUndefinedOrNull(); |
| } |
| |
| bool isCellConstant() |
| { |
| return isConstant() && constant()->value() && constant()->value().isCell(); |
| } |
| |
| JSCell* asCell() |
| { |
| return constant()->value().asCell(); |
| } |
| |
| template<typename T> |
| T dynamicCastConstant(VM& vm) |
| { |
| if (!isCellConstant()) |
| return nullptr; |
| return jsDynamicCast<T>(vm, asCell()); |
| } |
| |
| bool hasLazyJSValue() |
| { |
| return op() == LazyJSConstant; |
| } |
| |
| LazyJSValue lazyJSValue() |
| { |
| ASSERT(hasLazyJSValue()); |
| return *m_opInfo.as<LazyJSValue*>(); |
| } |
| |
| String tryGetString(Graph&); |
| |
| JSValue initializationValueForActivation() const |
| { |
| ASSERT(op() == CreateActivation); |
| return m_opInfo2.as<FrozenValue*>()->value(); |
| } |
| |
| bool hasArgumentsChild() |
| { |
| switch (op()) { |
| case GetMyArgumentByVal: |
| case GetMyArgumentByValOutOfBounds: |
| case LoadVarargs: |
| case ForwardVarargs: |
| case CallVarargs: |
| case CallForwardVarargs: |
| case ConstructVarargs: |
| case ConstructForwardVarargs: |
| case TailCallVarargs: |
| case TailCallForwardVarargs: |
| case TailCallVarargsInlinedCaller: |
| case TailCallForwardVarargsInlinedCaller: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| Edge& argumentsChild() |
| { |
| switch (op()) { |
| case GetMyArgumentByVal: |
| case GetMyArgumentByValOutOfBounds: |
| case LoadVarargs: |
| case ForwardVarargs: |
| return child1(); |
| case CallVarargs: |
| case CallForwardVarargs: |
| case ConstructVarargs: |
| case ConstructForwardVarargs: |
| case TailCallVarargs: |
| case TailCallForwardVarargs: |
| case TailCallVarargsInlinedCaller: |
| case TailCallForwardVarargsInlinedCaller: |
| return child3(); |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return child1(); |
| } |
| } |
| |
| bool containsMovHint() |
| { |
| switch (op()) { |
| case MovHint: |
| case ZombieHint: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool hasVariableAccessData(Graph&); |
| bool accessesStack(Graph& graph) |
| { |
| return hasVariableAccessData(graph); |
| } |
| |
| // This is useful for debugging code, where a node that should have a variable |
| // access data doesn't have one because it hasn't been initialized yet. |
| VariableAccessData* tryGetVariableAccessData() |
| { |
| VariableAccessData* result = m_opInfo.as<VariableAccessData*>(); |
| if (!result) |
| return 0; |
| return result->find(); |
| } |
| |
| VariableAccessData* variableAccessData() |
| { |
| return m_opInfo.as<VariableAccessData*>()->find(); |
| } |
| |
| VirtualRegister local() |
| { |
| return variableAccessData()->local(); |
| } |
| |
| VirtualRegister machineLocal() |
| { |
| return variableAccessData()->machineLocal(); |
| } |
| |
| bool hasUnlinkedLocal() |
| { |
| switch (op()) { |
| case ExtractOSREntryLocal: |
| case MovHint: |
| case ZombieHint: |
| case KillStack: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| VirtualRegister unlinkedLocal() |
| { |
| ASSERT(hasUnlinkedLocal()); |
| return VirtualRegister(m_opInfo.as<int32_t>()); |
| } |
| |
| bool hasStackAccessData() |
| { |
| switch (op()) { |
| case PutStack: |
| case GetStack: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| StackAccessData* stackAccessData() |
| { |
| ASSERT(hasStackAccessData()); |
| return m_opInfo.as<StackAccessData*>(); |
| } |
| |
| unsigned argumentCountIncludingThis() |
| { |
| ASSERT(op() == SetArgumentCountIncludingThis); |
| return m_opInfo.as<unsigned>(); |
| } |
| |
| bool hasPhi() |
| { |
| return op() == Upsilon; |
| } |
| |
| Node* phi() |
| { |
| ASSERT(hasPhi()); |
| return m_opInfo.as<Node*>(); |
| } |
| |
| bool isStoreBarrier() |
| { |
| return op() == StoreBarrier || op() == FencedStoreBarrier; |
| } |
| |
| bool hasIdentifier() |
| { |
| switch (op()) { |
| case TryGetById: |
| case GetById: |
| case GetByIdFlush: |
| case GetByIdWithThis: |
| case GetByIdDirect: |
| case GetByIdDirectFlush: |
| case PutById: |
| case PutByIdFlush: |
| case PutByIdDirect: |
| case PutByIdWithThis: |
| case PutGetterById: |
| case PutSetterById: |
| case PutGetterSetterById: |
| case DeleteById: |
| case InById: |
| case GetDynamicVar: |
| case PutDynamicVar: |
| case ResolveScopeForHoistingFuncDeclInEval: |
| case ResolveScope: |
| case ToObject: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| unsigned identifierNumber() |
| { |
| ASSERT(hasIdentifier()); |
| return m_opInfo.as<unsigned>(); |
| } |
| |
| bool hasGetPutInfo() |
| { |
| switch (op()) { |
| case GetDynamicVar: |
| case PutDynamicVar: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| unsigned getPutInfo() |
| { |
| ASSERT(hasGetPutInfo()); |
| return static_cast<unsigned>(m_opInfo.as<uint64_t>() >> 32); |
| } |
| |
| bool hasAccessorAttributes() |
| { |
| switch (op()) { |
| case PutGetterById: |
| case PutSetterById: |
| case PutGetterSetterById: |
| case PutGetterByVal: |
| case PutSetterByVal: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| int32_t accessorAttributes() |
| { |
| ASSERT(hasAccessorAttributes()); |
| switch (op()) { |
| case PutGetterById: |
| case PutSetterById: |
| case PutGetterSetterById: |
| return m_opInfo2.as<int32_t>(); |
| case PutGetterByVal: |
| case PutSetterByVal: |
| return m_opInfo.as<int32_t>(); |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| } |
| |
| bool hasPromotedLocationDescriptor() |
| { |
| return op() == PutHint; |
| } |
| |
| PromotedLocationDescriptor promotedLocationDescriptor(); |
| |
| // This corrects the arithmetic node flags, so that irrelevant bits are |
| // ignored. In particular, anything other than ArithMul or ValueMul does not need |
| // to know if it can speculate on negative zero. |
| NodeFlags arithNodeFlags() |
| { |
| NodeFlags result = m_flags & NodeArithFlagsMask; |
| if (op() == ArithMul || op() == ArithDiv || op() == ValueDiv || op() == ArithMod || op() == ArithNegate || op() == ArithPow || op() == ArithRound || op() == ArithFloor || op() == ArithCeil || op() == ArithTrunc || op() == DoubleAsInt32 || op() == ValueNegate || op() == ValueMul || op() == ValueDiv) |
| return result; |
| return result & ~NodeBytecodeNeedsNegZero; |
| } |
| |
| bool mayHaveNonIntResult() |
| { |
| return m_flags & NodeMayHaveNonIntResult; |
| } |
| |
| bool mayHaveDoubleResult() |
| { |
| return m_flags & NodeMayHaveDoubleResult; |
| } |
| |
| bool mayHaveNonNumericResult() |
| { |
| return m_flags & NodeMayHaveNonNumericResult; |
| } |
| |
| bool mayHaveBigIntResult() |
| { |
| return m_flags & NodeMayHaveBigIntResult; |
| } |
| |
| bool hasNewArrayBufferData() |
| { |
| return op() == NewArrayBuffer || op() == PhantomNewArrayBuffer; |
| } |
| |
| NewArrayBufferData newArrayBufferData() |
| { |
| ASSERT(hasNewArrayBufferData()); |
| return m_opInfo2.asNewArrayBufferData(); |
| } |
| |
| unsigned hasVectorLengthHint() |
| { |
| switch (op()) { |
| case NewArray: |
| case NewArrayBuffer: |
| case PhantomNewArrayBuffer: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| unsigned vectorLengthHint() |
| { |
| ASSERT(hasVectorLengthHint()); |
| if (op() == NewArray) |
| return m_opInfo2.as<unsigned>(); |
| return newArrayBufferData().vectorLengthHint; |
| } |
| |
| bool hasIndexingType() |
| { |
| switch (op()) { |
| case NewArray: |
| case NewArrayWithSize: |
| case NewArrayBuffer: |
| case PhantomNewArrayBuffer: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| BitVector* bitVector() |
| { |
| ASSERT(op() == NewArrayWithSpread || op() == PhantomNewArrayWithSpread); |
| return m_opInfo.as<BitVector*>(); |
| } |
| |
| // Return the indexing type that an array allocation *wants* to use. It may end up using a different |
| // type if we're having a bad time. You can determine the actual indexing type by asking the global |
| // object: |
| // |
| // m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType()) |
| // |
| // This will give you a Structure*, and that will have some indexing type that may be different from |
| // the this one. |
| IndexingType indexingType() |
| { |
| ASSERT(hasIndexingType()); |
| if (op() == NewArrayBuffer || op() == PhantomNewArrayBuffer) |
| return static_cast<IndexingType>(newArrayBufferData().indexingMode) & IndexingTypeMask; |
| return static_cast<IndexingType>(m_opInfo.as<uint32_t>()); |
| } |
| |
| IndexingType indexingMode() |
| { |
| ASSERT(hasIndexingType()); |
| if (op() == NewArrayBuffer || op() == PhantomNewArrayBuffer) |
| return static_cast<IndexingType>(newArrayBufferData().indexingMode); |
| return static_cast<IndexingType>(m_opInfo.as<uint32_t>()); |
| } |
| |
| bool hasTypedArrayType() |
| { |
| switch (op()) { |
| case NewTypedArray: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TypedArrayType typedArrayType() |
| { |
| ASSERT(hasTypedArrayType()); |
| TypedArrayType result = static_cast<TypedArrayType>(m_opInfo.as<uint32_t>()); |
| ASSERT(isTypedView(result)); |
| return result; |
| } |
| |
| bool hasInlineCapacity() |
| { |
| return op() == CreateThis; |
| } |
| |
| unsigned inlineCapacity() |
| { |
| ASSERT(hasInlineCapacity()); |
| return m_opInfo.as<unsigned>(); |
| } |
| |
| bool hasIsInternalPromise() |
| { |
| return op() == CreatePromise || op() == NewPromise; |
| } |
| |
| bool isInternalPromise() |
| { |
| ASSERT(hasIsInternalPromise()); |
| return m_opInfo2.as<bool>(); |
| } |
| |
| void setIndexingType(IndexingType indexingType) |
| { |
| ASSERT(hasIndexingType()); |
| m_opInfo = indexingType; |
| } |
| |
| bool hasScopeOffset() |
| { |
| return op() == GetClosureVar || op() == PutClosureVar; |
| } |
| |
| ScopeOffset scopeOffset() |
| { |
| ASSERT(hasScopeOffset()); |
| return ScopeOffset(m_opInfo.as<uint32_t>()); |
| } |
| |
| unsigned hasInternalFieldIndex() |
| { |
| return op() == GetInternalField || op() == PutInternalField; |
| } |
| |
| unsigned internalFieldIndex() |
| { |
| ASSERT(hasInternalFieldIndex()); |
| return m_opInfo.as<uint32_t>(); |
| } |
| |
| bool hasDirectArgumentsOffset() |
| { |
| return op() == GetFromArguments || op() == PutToArguments; |
| } |
| |
| DirectArgumentsOffset capturedArgumentsOffset() |
| { |
| ASSERT(hasDirectArgumentsOffset()); |
| return DirectArgumentsOffset(m_opInfo.as<uint32_t>()); |
| } |
| |
| bool hasRegisterPointer() |
| { |
| return op() == GetGlobalVar || op() == GetGlobalLexicalVariable || op() == PutGlobalVariable; |
| } |
| |
| WriteBarrier<Unknown>* variablePointer() |
| { |
| return m_opInfo.as<WriteBarrier<Unknown>*>(); |
| } |
| |
| bool hasCallVarargsData() |
| { |
| switch (op()) { |
| case CallVarargs: |
| case CallForwardVarargs: |
| case TailCallVarargs: |
| case TailCallForwardVarargs: |
| case TailCallVarargsInlinedCaller: |
| case TailCallForwardVarargsInlinedCaller: |
| case ConstructVarargs: |
| case ConstructForwardVarargs: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| CallVarargsData* callVarargsData() |
| { |
| ASSERT(hasCallVarargsData()); |
| return m_opInfo.as<CallVarargsData*>(); |
| } |
| |
| bool hasLoadVarargsData() |
| { |
| return op() == LoadVarargs || op() == ForwardVarargs; |
| } |
| |
| LoadVarargsData* loadVarargsData() |
| { |
| ASSERT(hasLoadVarargsData()); |
| return m_opInfo.as<LoadVarargsData*>(); |
| } |
| |
| InlineCallFrame* argumentsInlineCallFrame() |
| { |
| ASSERT(op() == GetArgumentCountIncludingThis); |
| return m_opInfo.as<InlineCallFrame*>(); |
| } |
| |
| bool hasQueriedType() |
| { |
| return op() == IsCellWithType; |
| } |
| |
| JSType queriedType() |
| { |
| static_assert(std::is_same<uint8_t, std::underlying_type<JSType>::type>::value, "Ensure that uint8_t is the underlying type for JSType."); |
| return static_cast<JSType>(m_opInfo.as<uint32_t>()); |
| } |
| |
| bool hasSpeculatedTypeForQuery() |
| { |
| return op() == IsCellWithType; |
| } |
| |
| Optional<SpeculatedType> speculatedTypeForQuery() |
| { |
| return speculationFromJSType(queriedType()); |
| } |
| |
| bool hasResult() |
| { |
| return !!result(); |
| } |
| |
| bool hasInt32Result() |
| { |
| return result() == NodeResultInt32; |
| } |
| |
| bool hasInt52Result() |
| { |
| return result() == NodeResultInt52; |
| } |
| |
| bool hasNumberResult() |
| { |
| return result() == NodeResultNumber; |
| } |
| |
| bool hasNumberOrAnyIntResult() |
| { |
| return hasNumberResult() || hasInt32Result() || hasInt52Result(); |
| } |
| |
| bool hasNumericResult() |
| { |
| switch (op()) { |
| case ValueSub: |
| case ValueMul: |
| case ValueBitAnd: |
| case ValueBitOr: |
| case ValueBitXor: |
| case ValueBitNot: |
| case ValueBitLShift: |
| case ValueBitRShift: |
| case ValueNegate: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool hasDoubleResult() |
| { |
| return result() == NodeResultDouble; |
| } |
| |
| bool hasJSResult() |
| { |
| return result() == NodeResultJS; |
| } |
| |
| bool hasBooleanResult() |
| { |
| return result() == NodeResultBoolean; |
| } |
| |
| bool hasStorageResult() |
| { |
| return result() == NodeResultStorage; |
| } |
| |
| UseKind defaultUseKind() |
| { |
| return useKindForResult(result()); |
| } |
| |
| Edge defaultEdge() |
| { |
| return Edge(this, defaultUseKind()); |
| } |
| |
| bool isJump() |
| { |
| return op() == Jump; |
| } |
| |
| bool isBranch() |
| { |
| return op() == Branch; |
| } |
| |
| bool isSwitch() const |
| { |
| return op() == Switch; |
| } |
| |
| bool isEntrySwitch() const |
| { |
| return op() == EntrySwitch; |
| } |
| |
| bool isTerminal() |
| { |
| switch (op()) { |
| case Jump: |
| case Branch: |
| case Switch: |
| case EntrySwitch: |
| case Return: |
| case TailCall: |
| case DirectTailCall: |
| case TailCallVarargs: |
| case TailCallForwardVarargs: |
| case Unreachable: |
| case Throw: |
| case ThrowStaticError: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool isFunctionTerminal() |
| { |
| if (isTerminal() && !numSuccessors()) |
| return true; |
| |
| return false; |
| } |
| |
| // As is described in DFGNodeType.h's ForceOSRExit, this is a pseudo-terminal. |
| // It means that execution should fall out of DFG at this point, but execution |
| // does continue in the basic block - just in a different compiler. |
| // FIXME: This is used for lightweight reachability decision. But this should |
| // be replaced with AI-based reachability ideally. |
| bool isPseudoTerminal() |
| { |
| switch (op()) { |
| case ForceOSRExit: |
| case CheckBadCell: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| unsigned targetBytecodeOffsetDuringParsing() |
| { |
| ASSERT(isJump()); |
| return m_opInfo.as<unsigned>(); |
| } |
| |
| BasicBlock*& targetBlock() |
| { |
| ASSERT(isJump()); |
| return *bitwise_cast<BasicBlock**>(&m_opInfo.u.pointer); |
| } |
| |
| BranchData* branchData() |
| { |
| ASSERT(isBranch()); |
| return m_opInfo.as<BranchData*>(); |
| } |
| |
| SwitchData* switchData() |
| { |
| ASSERT(isSwitch()); |
| return m_opInfo.as<SwitchData*>(); |
| } |
| |
| EntrySwitchData* entrySwitchData() |
| { |
| ASSERT(isEntrySwitch()); |
| return m_opInfo.as<EntrySwitchData*>(); |
| } |
| |
| Intrinsic intrinsic() |
| { |
| RELEASE_ASSERT(op() == CPUIntrinsic); |
| return m_opInfo.as<Intrinsic>(); |
| } |
| |
| unsigned numSuccessors() |
| { |
| switch (op()) { |
| case Jump: |
| return 1; |
| case Branch: |
| return 2; |
| case Switch: |
| return switchData()->cases.size() + 1; |
| case EntrySwitch: |
| return entrySwitchData()->cases.size(); |
| default: |
| return 0; |
| } |
| } |
| |
| BasicBlock*& successor(unsigned index) |
| { |
| if (isSwitch()) { |
| if (index < switchData()->cases.size()) |
| return switchData()->cases[index].target.block; |
| RELEASE_ASSERT(index == switchData()->cases.size()); |
| return switchData()->fallThrough.block; |
| } else if (isEntrySwitch()) |
| return entrySwitchData()->cases[index]; |
| |
| switch (index) { |
| case 0: |
| if (isJump()) |
| return targetBlock(); |
| return branchData()->taken.block; |
| case 1: |
| return branchData()->notTaken.block; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return targetBlock(); |
| } |
| } |
| |
| class SuccessorsIterable { |
| public: |
| SuccessorsIterable() |
| : m_terminal(nullptr) |
| { |
| } |
| |
| SuccessorsIterable(Node* terminal) |
| : m_terminal(terminal) |
| { |
| } |
| |
| class iterator { |
| public: |
| iterator() |
| : m_terminal(nullptr) |
| , m_index(UINT_MAX) |
| { |
| } |
| |
| iterator(Node* terminal, unsigned index) |
| : m_terminal(terminal) |
| , m_index(index) |
| { |
| } |
| |
| BasicBlock* operator*() |
| { |
| return m_terminal->successor(m_index); |
| } |
| |
| iterator& operator++() |
| { |
| m_index++; |
| return *this; |
| } |
| |
| bool operator==(const iterator& other) const |
| { |
| return m_index == other.m_index; |
| } |
| |
| bool operator!=(const iterator& other) const |
| { |
| return !(*this == other); |
| } |
| private: |
| Node* m_terminal; |
| unsigned m_index; |
| }; |
| |
| iterator begin() |
| { |
| return iterator(m_terminal, 0); |
| } |
| |
| iterator end() |
| { |
| return iterator(m_terminal, m_terminal->numSuccessors()); |
| } |
| |
| size_t size() const { return m_terminal->numSuccessors(); } |
| BasicBlock* at(size_t index) const { return m_terminal->successor(index); } |
| BasicBlock* operator[](size_t index) const { return at(index); } |
| |
| private: |
| Node* m_terminal; |
| }; |
| |
| SuccessorsIterable successors() |
| { |
| return SuccessorsIterable(this); |
| } |
| |
| BasicBlock*& successorForCondition(bool condition) |
| { |
| return branchData()->forCondition(condition); |
| } |
| |
| bool hasHeapPrediction() |
| { |
| switch (op()) { |
| case ArithAbs: |
| case ArithRound: |
| case ArithFloor: |
| case ArithCeil: |
| case ArithTrunc: |
| case GetDirectPname: |
| case GetById: |
| case GetByIdFlush: |
| case GetByIdWithThis: |
| case GetByIdDirect: |
| case GetByIdDirectFlush: |
| case GetPrototypeOf: |
| case TryGetById: |
| case GetByVal: |
| case GetByValWithThis: |
| case Call: |
| case DirectCall: |
| case TailCallInlinedCaller: |
| case DirectTailCallInlinedCaller: |
| case Construct: |
| case DirectConstruct: |
| case CallVarargs: |
| case CallEval: |
| case TailCallVarargsInlinedCaller: |
| case ConstructVarargs: |
| case CallForwardVarargs: |
| case TailCallForwardVarargsInlinedCaller: |
| case GetByOffset: |
| case MultiGetByOffset: |
| case GetClosureVar: |
| case GetInternalField: |
| case GetFromArguments: |
| case GetArgument: |
| case ArrayPop: |
| case ArrayPush: |
| case RegExpExec: |
| case RegExpExecNonGlobalOrSticky: |
| case RegExpTest: |
| case RegExpMatchFast: |
| case RegExpMatchFastGlobal: |
| case GetGlobalVar: |
| case GetGlobalLexicalVariable: |
| case StringReplace: |
| case StringReplaceRegExp: |
| case ToNumber: |
| case ToObject: |
| case ValueBitAnd: |
| case ValueBitOr: |
| case ValueBitXor: |
| case ValueBitNot: |
| case ValueBitLShift: |
| case ValueBitRShift: |
| case CallObjectConstructor: |
| case LoadKeyFromMapBucket: |
| case LoadValueFromMapBucket: |
| case CallDOMGetter: |
| case CallDOM: |
| case ParseInt: |
| case AtomicsAdd: |
| case AtomicsAnd: |
| case AtomicsCompareExchange: |
| case AtomicsExchange: |
| case AtomicsLoad: |
| case AtomicsOr: |
| case AtomicsStore: |
| case AtomicsSub: |
| case AtomicsXor: |
| case GetDynamicVar: |
| case ExtractValueFromWeakMapGet: |
| case ToThis: |
| case DataViewGetInt: |
| case DataViewGetFloat: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| SpeculatedType getHeapPrediction() |
| { |
| ASSERT(hasHeapPrediction()); |
| return m_opInfo2.as<SpeculatedType>(); |
| } |
| |
| void setHeapPrediction(SpeculatedType prediction) |
| { |
| ASSERT(hasHeapPrediction()); |
| m_opInfo2 = prediction; |
| } |
| |
| SpeculatedType getForcedPrediction() |
| { |
| ASSERT(op() == IdentityWithProfile); |
| return m_opInfo.as<SpeculatedType>(); |
| } |
| |
| uint32_t catchOSREntryIndex() const |
| { |
| ASSERT(op() == ExtractCatchLocal); |
| return m_opInfo.as<uint32_t>(); |
| } |
| |
| SpeculatedType catchLocalPrediction() |
| { |
| ASSERT(op() == ExtractCatchLocal); |
| return m_opInfo2.as<SpeculatedType>(); |
| } |
| |
| bool hasCellOperand() |
| { |
| switch (op()) { |
| case CheckCell: |
| case OverridesHasInstance: |
| case NewFunction: |
| case NewGeneratorFunction: |
| case NewAsyncFunction: |
| case NewAsyncGeneratorFunction: |
| case CreateActivation: |
| case MaterializeCreateActivation: |
| case NewRegexp: |
| case NewArrayBuffer: |
| case PhantomNewArrayBuffer: |
| case CompareEqPtr: |
| case CallObjectConstructor: |
| case DirectCall: |
| case DirectTailCall: |
| case DirectConstruct: |
| case DirectTailCallInlinedCaller: |
| case RegExpExecNonGlobalOrSticky: |
| case RegExpMatchFastGlobal: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| FrozenValue* cellOperand() |
| { |
| ASSERT(hasCellOperand()); |
| return m_opInfo.as<FrozenValue*>(); |
| } |
| |
| template<typename T> |
| T castOperand() |
| { |
| return cellOperand()->cast<T>(); |
| } |
| |
| void setCellOperand(FrozenValue* value) |
| { |
| ASSERT(hasCellOperand()); |
| m_opInfo = value; |
| } |
| |
| bool hasWatchpointSet() |
| { |
| return op() == NotifyWrite; |
| } |
| |
| WatchpointSet* watchpointSet() |
| { |
| ASSERT(hasWatchpointSet()); |
| return m_opInfo.as<WatchpointSet*>(); |
| } |
| |
| bool hasStoragePointer() |
| { |
| return op() == ConstantStoragePointer; |
| } |
| |
| void* storagePointer() |
| { |
| ASSERT(hasStoragePointer()); |
| return m_opInfo.as<void*>(); |
| } |
| |
| bool hasUidOperand() |
| { |
| return op() == CheckStringIdent; |
| } |
| |
| UniquedStringImpl* uidOperand() |
| { |
| ASSERT(hasUidOperand()); |
| return m_opInfo.as<UniquedStringImpl*>(); |
| } |
| |
| bool hasTypeInfoOperand() |
| { |
| return op() == CheckTypeInfoFlags; |
| } |
| |
| unsigned typeInfoOperand() |
| { |
| ASSERT(hasTypeInfoOperand() && m_opInfo.as<uint32_t>() <= static_cast<uint32_t>(UCHAR_MAX)); |
| return m_opInfo.as<uint32_t>(); |
| } |
| |
| bool hasTransition() |
| { |
| switch (op()) { |
| case PutStructure: |
| case AllocatePropertyStorage: |
| case ReallocatePropertyStorage: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| Transition* transition() |
| { |
| ASSERT(hasTransition()); |
| return m_opInfo.as<Transition*>(); |
| } |
| |
| bool hasStructureSet() |
| { |
| switch (op()) { |
| case CheckStructure: |
| case CheckStructureOrEmpty: |
| case CheckStructureImmediate: |
| case MaterializeNewObject: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| const RegisteredStructureSet& structureSet() |
| { |
| ASSERT(hasStructureSet()); |
| return *m_opInfo.as<RegisteredStructureSet*>(); |
| } |
| |
| bool hasStructure() |
| { |
| switch (op()) { |
| case ArrayifyToStructure: |
| case NewObject: |
| case NewPromise: |
| case NewGenerator: |
| case NewAsyncGenerator: |
| case NewStringObject: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| RegisteredStructure structure() |
| { |
| ASSERT(hasStructure()); |
| return m_opInfo.asRegisteredStructure(); |
| } |
| |
| bool hasStorageAccessData() |
| { |
| switch (op()) { |
| case GetByOffset: |
| case PutByOffset: |
| case GetGetterSetterByOffset: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| StorageAccessData& storageAccessData() |
| { |
| ASSERT(hasStorageAccessData()); |
| return *m_opInfo.as<StorageAccessData*>(); |
| } |
| |
| bool hasMultiGetByOffsetData() |
| { |
| return op() == MultiGetByOffset; |
| } |
| |
| MultiGetByOffsetData& multiGetByOffsetData() |
| { |
| ASSERT(hasMultiGetByOffsetData()); |
| return *m_opInfo.as<MultiGetByOffsetData*>(); |
| } |
| |
| bool hasMultiPutByOffsetData() |
| { |
| return op() == MultiPutByOffset; |
| } |
| |
| MultiPutByOffsetData& multiPutByOffsetData() |
| { |
| ASSERT(hasMultiPutByOffsetData()); |
| return *m_opInfo.as<MultiPutByOffsetData*>(); |
| } |
| |
| bool hasMatchStructureData() |
| { |
| return op() == MatchStructure; |
| } |
| |
| MatchStructureData& matchStructureData() |
| { |
| ASSERT(hasMatchStructureData()); |
| return *m_opInfo.as<MatchStructureData*>(); |
| } |
| |
| bool hasObjectMaterializationData() |
| { |
| switch (op()) { |
| case MaterializeNewObject: |
| case MaterializeCreateActivation: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| ObjectMaterializationData& objectMaterializationData() |
| { |
| ASSERT(hasObjectMaterializationData()); |
| return *m_opInfo2.as<ObjectMaterializationData*>(); |
| } |
| |
| bool isObjectAllocation() |
| { |
| switch (op()) { |
| case NewObject: |
| case MaterializeNewObject: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool isPhantomObjectAllocation() |
| { |
| switch (op()) { |
| case PhantomNewObject: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool isActivationAllocation() |
| { |
| switch (op()) { |
| case CreateActivation: |
| case MaterializeCreateActivation: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool isPhantomActivationAllocation() |
| { |
| switch (op()) { |
| case PhantomCreateActivation: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool isFunctionAllocation() |
| { |
| switch (op()) { |
| case NewFunction: |
| case NewGeneratorFunction: |
| case NewAsyncGeneratorFunction: |
| case NewAsyncFunction: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool isPhantomFunctionAllocation() |
| { |
| switch (op()) { |
| case PhantomNewFunction: |
| case PhantomNewGeneratorFunction: |
| case PhantomNewAsyncFunction: |
| case PhantomNewAsyncGeneratorFunction: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool isPhantomAllocation() |
| { |
| switch (op()) { |
| case PhantomNewObject: |
| case PhantomDirectArguments: |
| case PhantomCreateRest: |
| case PhantomSpread: |
| case PhantomNewArrayWithSpread: |
| case PhantomNewArrayBuffer: |
| case PhantomClonedArguments: |
| case PhantomNewFunction: |
| case PhantomNewGeneratorFunction: |
| case PhantomNewAsyncFunction: |
| case PhantomNewAsyncGeneratorFunction: |
| case PhantomCreateActivation: |
| case PhantomNewRegexp: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool hasArrayMode() |
| { |
| switch (op()) { |
| case GetIndexedPropertyStorage: |
| case GetArrayLength: |
| case GetVectorLength: |
| case InByVal: |
| case PutByValDirect: |
| case PutByVal: |
| case PutByValAlias: |
| case GetByVal: |
| case StringCharAt: |
| case StringCharCodeAt: |
| case StringCodePointAt: |
| case CheckArray: |
| case Arrayify: |
| case ArrayifyToStructure: |
| case ArrayPush: |
| case ArrayPop: |
| case ArrayIndexOf: |
| case HasIndexedProperty: |
| case AtomicsAdd: |
| case AtomicsAnd: |
| case AtomicsCompareExchange: |
| case AtomicsExchange: |
| case AtomicsLoad: |
| case AtomicsOr: |
| case AtomicsStore: |
| case AtomicsSub: |
| case AtomicsXor: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| ArrayMode arrayMode() |
| { |
| ASSERT(hasArrayMode()); |
| if (op() == ArrayifyToStructure) |
| return ArrayMode::fromWord(m_opInfo2.as<uint32_t>()); |
| return ArrayMode::fromWord(m_opInfo.as<uint32_t>()); |
| } |
| |
| bool setArrayMode(ArrayMode arrayMode) |
| { |
| ASSERT(hasArrayMode()); |
| if (this->arrayMode() == arrayMode) |
| return false; |
| m_opInfo = arrayMode.asWord(); |
| return true; |
| } |
| |
| bool hasArithMode() |
| { |
| switch (op()) { |
| case ArithAbs: |
| case ArithAdd: |
| case ArithSub: |
| case ArithNegate: |
| case ArithMul: |
| case ArithDiv: |
| case ArithMod: |
| case UInt32ToNumber: |
| case DoubleAsInt32: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| Arith::Mode arithMode() |
| { |
| ASSERT(hasArithMode()); |
| return static_cast<Arith::Mode>(m_opInfo.as<uint32_t>()); |
| } |
| |
| void setArithMode(Arith::Mode mode) |
| { |
| m_opInfo = mode; |
| } |
| |
| bool hasArithRoundingMode() |
| { |
| return op() == ArithRound || op() == ArithFloor || op() == ArithCeil || op() == ArithTrunc; |
| } |
| |
| Arith::RoundingMode arithRoundingMode() |
| { |
| ASSERT(hasArithRoundingMode()); |
| return static_cast<Arith::RoundingMode>(m_opInfo.as<uint32_t>()); |
| } |
| |
| void setArithRoundingMode(Arith::RoundingMode mode) |
| { |
| ASSERT(hasArithRoundingMode()); |
| m_opInfo = static_cast<uint32_t>(mode); |
| } |
| |
| bool hasArithUnaryType() |
| { |
| return op() == ArithUnary; |
| } |
| |
| Arith::UnaryType arithUnaryType() |
| { |
| ASSERT(hasArithUnaryType()); |
| return static_cast<Arith::UnaryType>(m_opInfo.as<uint32_t>()); |
| } |
| |
| bool hasVirtualRegister() |
| { |
| return m_virtualRegister.isValid(); |
| } |
| |
| VirtualRegister virtualRegister() |
| { |
| ASSERT(hasResult()); |
| ASSERT(m_virtualRegister.isValid()); |
| return m_virtualRegister; |
| } |
| |
| void setVirtualRegister(VirtualRegister virtualRegister) |
| { |
| ASSERT(hasResult()); |
| ASSERT(!m_virtualRegister.isValid()); |
| m_virtualRegister = virtualRegister; |
| } |
| |
| bool hasExecutionCounter() |
| { |
| return op() == CountExecution; |
| } |
| |
| Profiler::ExecutionCounter* executionCounter() |
| { |
| return m_opInfo.as<Profiler::ExecutionCounter*>(); |
| } |
| |
| unsigned entrypointIndex() |
| { |
| ASSERT(op() == InitializeEntrypointArguments); |
| return m_opInfo.as<unsigned>(); |
| } |
| |
| DataViewData dataViewData() |
| { |
| ASSERT(op() == DataViewGetInt || op() == DataViewGetFloat || op() == DataViewSet); |
| return bitwise_cast<DataViewData>(m_opInfo.as<uint64_t>()); |
| } |
| |
| bool shouldGenerate() |
| { |
| return m_refCount; |
| } |
| |
| // Return true if the execution of this Node does not affect our ability to OSR to the FTL. |
| // FIXME: Isn't this just like checking if the node has effects? |
| bool isSemanticallySkippable() |
| { |
| return op() == CountExecution || op() == InvalidationPoint; |
| } |
| |
| unsigned refCount() |
| { |
| return m_refCount; |
| } |
| |
| unsigned postfixRef() |
| { |
| return m_refCount++; |
| } |
| |
| unsigned adjustedRefCount() |
| { |
| return mustGenerate() ? m_refCount - 1 : m_refCount; |
| } |
| |
| void setRefCount(unsigned refCount) |
| { |
| m_refCount = refCount; |
| } |
| |
| Edge& child1() |
| { |
| ASSERT(!(m_flags & NodeHasVarArgs)); |
| return children.child1(); |
| } |
| |
| // This is useful if you want to do a fast check on the first child |
| // before also doing a check on the opcode. Use this with care and |
| // avoid it if possible. |
| Edge child1Unchecked() |
| { |
| return children.child1Unchecked(); |
| } |
| |
| Edge& child2() |
| { |
| ASSERT(!(m_flags & NodeHasVarArgs)); |
| return children.child2(); |
| } |
| |
| Edge& child3() |
| { |
| ASSERT(!(m_flags & NodeHasVarArgs)); |
| return children.child3(); |
| } |
| |
| unsigned firstChild() |
| { |
| ASSERT(m_flags & NodeHasVarArgs); |
| return children.firstChild(); |
| } |
| |
| unsigned numChildren() |
| { |
| ASSERT(m_flags & NodeHasVarArgs); |
| return children.numChildren(); |
| } |
| |
| UseKind binaryUseKind() |
| { |
| ASSERT(child1().useKind() == child2().useKind()); |
| return child1().useKind(); |
| } |
| |
| bool isBinaryUseKind(UseKind left, UseKind right) |
| { |
| return child1().useKind() == left && child2().useKind() == right; |
| } |
| |
| bool isBinaryUseKind(UseKind useKind) |
| { |
| return isBinaryUseKind(useKind, useKind); |
| } |
| |
| Edge childFor(UseKind useKind) |
| { |
| if (child1().useKind() == useKind) |
| return child1(); |
| if (child2().useKind() == useKind) |
| return child2(); |
| if (child3().useKind() == useKind) |
| return child3(); |
| return Edge(); |
| } |
| |
| SpeculatedType prediction() |
| { |
| return m_prediction; |
| } |
| |
| bool predict(SpeculatedType prediction) |
| { |
| return mergeSpeculation(m_prediction, prediction); |
| } |
| |
| bool shouldSpeculateInt32() |
| { |
| return isInt32Speculation(prediction()); |
| } |
| |
| bool shouldSpeculateNotInt32() |
| { |
| return isNotInt32Speculation(prediction()); |
| } |
| |
| bool sawBooleans() |
| { |
| return !!(prediction() & SpecBoolean); |
| } |
| |
| bool shouldSpeculateInt32OrBoolean() |
| { |
| return isInt32OrBooleanSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateInt32ForArithmetic() |
| { |
| return isInt32SpeculationForArithmetic(prediction()); |
| } |
| |
| bool shouldSpeculateInt32OrBooleanForArithmetic() |
| { |
| return isInt32OrBooleanSpeculationForArithmetic(prediction()); |
| } |
| |
| bool shouldSpeculateInt32OrBooleanExpectingDefined() |
| { |
| return isInt32OrBooleanSpeculationExpectingDefined(prediction()); |
| } |
| |
| bool shouldSpeculateInt52() |
| { |
| // We have to include SpecInt32Only here for two reasons: |
| // 1. We diligently write code that first checks if we should speculate Int32. |
| // For example: |
| // if (shouldSpeculateInt32()) ... |
| // else if (shouldSpeculateInt52()) ... |
| // This means we it's totally valid to speculate Int52 when we're dealing |
| // with a type that's the union of Int32 and Int52. |
| // |
| // It would be a performance mistake to not include Int32 here because we obviously |
| // have variables that are the union of Int32 and Int52 values, and it's better |
| // to speculate Int52 than double in that situation. |
| // |
| // 2. We also write code where we ask if the inputs can be Int52, like if |
| // we know via profiling that an Add overflows, we may not emit an Int32 add. |
| // However, we only emit such an add if both inputs can be Int52, and Int32 |
| // can trivially become Int52. |
| // |
| return enableInt52() && isInt32OrInt52Speculation(prediction()); |
| } |
| |
| bool shouldSpeculateDouble() |
| { |
| return isDoubleSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateDoubleReal() |
| { |
| return isDoubleRealSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateNumber() |
| { |
| return isFullNumberSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateNumberOrBoolean() |
| { |
| return isFullNumberOrBooleanSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateNumberOrBooleanExpectingDefined() |
| { |
| return isFullNumberOrBooleanSpeculationExpectingDefined(prediction()); |
| } |
| |
| bool shouldSpeculateBoolean() |
| { |
| return isBooleanSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateNotBoolean() |
| { |
| return isNotBooleanSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateOther() |
| { |
| return isOtherSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateMisc() |
| { |
| return isMiscSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateStringIdent() |
| { |
| return isStringIdentSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateNotStringVar() |
| { |
| return isNotStringVarSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateString() |
| { |
| return isStringSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateNotString() |
| { |
| return isNotStringSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateStringOrOther() |
| { |
| return isStringOrOtherSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateStringObject() |
| { |
| return isStringObjectSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateStringOrStringObject() |
| { |
| return isStringOrStringObjectSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateRegExpObject() |
| { |
| return isRegExpObjectSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateSymbol() |
| { |
| return isSymbolSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateBigInt() |
| { |
| return isBigIntSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateFinalObject() |
| { |
| return isFinalObjectSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateFinalObjectOrOther() |
| { |
| return isFinalObjectOrOtherSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateArray() |
| { |
| return isArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateFunction() |
| { |
| return isFunctionSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateProxyObject() |
| { |
| return isProxyObjectSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateDerivedArray() |
| { |
| return isDerivedArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateDirectArguments() |
| { |
| return isDirectArgumentsSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateScopedArguments() |
| { |
| return isScopedArgumentsSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateInt8Array() |
| { |
| return isInt8ArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateInt16Array() |
| { |
| return isInt16ArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateInt32Array() |
| { |
| return isInt32ArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateUint8Array() |
| { |
| return isUint8ArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateUint8ClampedArray() |
| { |
| return isUint8ClampedArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateUint16Array() |
| { |
| return isUint16ArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateUint32Array() |
| { |
| return isUint32ArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateFloat32Array() |
| { |
| return isFloat32ArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateFloat64Array() |
| { |
| return isFloat64ArraySpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateArrayOrOther() |
| { |
| return isArrayOrOtherSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateObject() |
| { |
| return isObjectSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateObjectOrOther() |
| { |
| return isObjectOrOtherSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateCell() |
| { |
| return isCellSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateCellOrOther() |
| { |
| return isCellOrOtherSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateNotCell() |
| { |
| return isNotCellSpeculation(prediction()); |
| } |
| |
| bool shouldSpeculateUntypedForArithmetic() |
| { |
| return isUntypedSpeculationForArithmetic(prediction()); |
| } |
| |
| static bool shouldSpeculateUntypedForArithmetic(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateUntypedForArithmetic() || op2->shouldSpeculateUntypedForArithmetic(); |
| } |
| |
| bool shouldSpeculateUntypedForBitOps() |
| { |
| return isUntypedSpeculationForBitOps(prediction()); |
| } |
| |
| static bool shouldSpeculateUntypedForBitOps(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateUntypedForBitOps() || op2->shouldSpeculateUntypedForBitOps(); |
| } |
| |
| static bool shouldSpeculateBoolean(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateBoolean() && op2->shouldSpeculateBoolean(); |
| } |
| |
| static bool shouldSpeculateInt32(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateInt32() && op2->shouldSpeculateInt32(); |
| } |
| |
| static bool shouldSpeculateInt32OrBoolean(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateInt32OrBoolean() |
| && op2->shouldSpeculateInt32OrBoolean(); |
| } |
| |
| static bool shouldSpeculateInt32OrBooleanForArithmetic(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateInt32OrBooleanForArithmetic() |
| && op2->shouldSpeculateInt32OrBooleanForArithmetic(); |
| } |
| |
| static bool shouldSpeculateInt32OrBooleanExpectingDefined(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateInt32OrBooleanExpectingDefined() |
| && op2->shouldSpeculateInt32OrBooleanExpectingDefined(); |
| } |
| |
| static bool shouldSpeculateInt52(Node* op1, Node* op2) |
| { |
| return enableInt52() && op1->shouldSpeculateInt52() && op2->shouldSpeculateInt52(); |
| } |
| |
| static bool shouldSpeculateNumber(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateNumber() && op2->shouldSpeculateNumber(); |
| } |
| |
| static bool shouldSpeculateNumberOrBoolean(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateNumberOrBoolean() |
| && op2->shouldSpeculateNumberOrBoolean(); |
| } |
| |
| static bool shouldSpeculateNumberOrBooleanExpectingDefined(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateNumberOrBooleanExpectingDefined() |
| && op2->shouldSpeculateNumberOrBooleanExpectingDefined(); |
| } |
| |
| static bool shouldSpeculateSymbol(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateSymbol() && op2->shouldSpeculateSymbol(); |
| } |
| |
| static bool shouldSpeculateBigInt(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateBigInt() && op2->shouldSpeculateBigInt(); |
| } |
| |
| static bool shouldSpeculateFinalObject(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateFinalObject() && op2->shouldSpeculateFinalObject(); |
| } |
| |
| static bool shouldSpeculateArray(Node* op1, Node* op2) |
| { |
| return op1->shouldSpeculateArray() && op2->shouldSpeculateArray(); |
| } |
| |
| bool canSpeculateInt32(RareCaseProfilingSource source) |
| { |
| return nodeCanSpeculateInt32(arithNodeFlags(), source); |
| } |
| |
| bool canSpeculateInt52(RareCaseProfilingSource source) |
| { |
| return nodeCanSpeculateInt52(arithNodeFlags(), source); |
| } |
| |
| RareCaseProfilingSource sourceFor(PredictionPass pass) |
| { |
| if (pass == PrimaryPass || child1()->sawBooleans() || (child2() && child2()->sawBooleans())) |
| return DFGRareCase; |
| return AllRareCases; |
| } |
| |
| bool canSpeculateInt32(PredictionPass pass) |
| { |
| return canSpeculateInt32(sourceFor(pass)); |
| } |
| |
| bool canSpeculateInt52(PredictionPass pass) |
| { |
| return canSpeculateInt52(sourceFor(pass)); |
| } |
| |
| bool hasTypeLocation() |
| { |
| return op() == ProfileType; |
| } |
| |
| TypeLocation* typeLocation() |
| { |
| ASSERT(hasTypeLocation()); |
| return m_opInfo.as<TypeLocation*>(); |
| } |
| |
| bool hasBasicBlockLocation() |
| { |
| return op() == ProfileControlFlow; |
| } |
| |
| BasicBlockLocation* basicBlockLocation() |
| { |
| ASSERT(hasBasicBlockLocation()); |
| return m_opInfo.as<BasicBlockLocation*>(); |
| } |
| |
| bool hasCallDOMGetterData() const |
| { |
| return op() == CallDOMGetter; |
| } |
| |
| CallDOMGetterData* callDOMGetterData() |
| { |
| ASSERT(hasCallDOMGetterData()); |
| return m_opInfo.as<CallDOMGetterData*>(); |
| } |
| |
| bool hasClassInfo() const |
| { |
| return op() == CheckSubClass; |
| } |
| |
| const ClassInfo* classInfo() |
| { |
| return m_opInfo.as<const ClassInfo*>(); |
| } |
| |
| bool hasSignature() const |
| { |
| // Note that this does not include TailCall node types intentionally. |
| // CallDOM node types are always converted from Call. |
| return op() == Call || op() == CallDOM; |
| } |
| |
| const DOMJIT::Signature* signature() |
| { |
| return m_opInfo.as<const DOMJIT::Signature*>(); |
| } |
| |
| bool hasInternalMethodType() const |
| { |
| return op() == HasIndexedProperty; |
| } |
| |
| PropertySlot::InternalMethodType internalMethodType() const |
| { |
| ASSERT(hasInternalMethodType()); |
| return static_cast<PropertySlot::InternalMethodType>(m_opInfo2.as<uint32_t>()); |
| } |
| |
| void setInternalMethodType(PropertySlot::InternalMethodType type) |
| { |
| ASSERT(hasInternalMethodType()); |
| m_opInfo2 = static_cast<uint32_t>(type); |
| } |
| |
| Node* replacement() const |
| { |
| return m_misc.replacement; |
| } |
| |
| void setReplacement(Node* replacement) |
| { |
| m_misc.replacement = replacement; |
| } |
| |
| Epoch epoch() const |
| { |
| return Epoch::fromUnsigned(m_misc.epoch); |
| } |
| |
| void setEpoch(Epoch epoch) |
| { |
| m_misc.epoch = epoch.toUnsigned(); |
| } |
| |
| bool hasNumberOfArgumentsToSkip() |
| { |
| return op() == CreateRest || op() == PhantomCreateRest || op() == GetRestLength || op() == GetMyArgumentByVal || op() == GetMyArgumentByValOutOfBounds; |
| } |
| |
| unsigned numberOfArgumentsToSkip() |
| { |
| ASSERT(hasNumberOfArgumentsToSkip()); |
| return m_opInfo.as<unsigned>(); |
| } |
| |
| bool hasArgumentIndex() |
| { |
| return op() == GetArgument; |
| } |
| |
| unsigned argumentIndex() |
| { |
| ASSERT(hasArgumentIndex()); |
| return m_opInfo.as<unsigned>(); |
| } |
| |
| bool hasBucketOwnerType() |
| { |
| return op() == GetMapBucketNext || op() == LoadKeyFromMapBucket || op() == LoadValueFromMapBucket; |
| } |
| |
| BucketOwnerType bucketOwnerType() |
| { |
| ASSERT(hasBucketOwnerType()); |
| return m_opInfo.as<BucketOwnerType>(); |
| } |
| |
| bool hasValidRadixConstant() |
| { |
| return op() == NumberToStringWithValidRadixConstant; |
| } |
| |
| int32_t validRadixConstant() |
| { |
| ASSERT(hasValidRadixConstant()); |
| return m_opInfo.as<int32_t>(); |
| } |
| |
| bool hasIgnoreLastIndexIsWritable() |
| { |
| return op() == SetRegExpObjectLastIndex; |
| } |
| |
| bool ignoreLastIndexIsWritable() |
| { |
| ASSERT(hasIgnoreLastIndexIsWritable()); |
| return m_opInfo.as<uint32_t>(); |
| } |
| |
| uint32_t errorType() |
| { |
| ASSERT(op() == ThrowStaticError); |
| return m_opInfo.as<uint32_t>(); |
| } |
| |
| bool hasCallLinkStatus() |
| { |
| return op() == FilterCallLinkStatus; |
| } |
| |
| CallLinkStatus* callLinkStatus() |
| { |
| ASSERT(hasCallLinkStatus()); |
| return m_opInfo.as<CallLinkStatus*>(); |
| } |
| |
| bool hasGetByIdStatus() |
| { |
| return op() == FilterGetByIdStatus; |
| } |
| |
| GetByIdStatus* getByIdStatus() |
| { |
| ASSERT(hasGetByIdStatus()); |
| return m_opInfo.as<GetByIdStatus*>(); |
| } |
| |
| bool hasInByIdStatus() |
| { |
| return op() == FilterInByIdStatus; |
| } |
| |
| InByIdStatus* inByIdStatus() |
| { |
| ASSERT(hasInByIdStatus()); |
| return m_opInfo.as<InByIdStatus*>(); |
| } |
| |
| bool hasPutByIdStatus() |
| { |
| return op() == FilterPutByIdStatus; |
| } |
| |
| PutByIdStatus* putByIdStatus() |
| { |
| ASSERT(hasPutByIdStatus()); |
| return m_opInfo.as<PutByIdStatus*>(); |
| } |
| |
| void dumpChildren(PrintStream& out) |
| { |
| if (!child1()) |
| return; |
| out.printf("@%u", child1()->index()); |
| if (!child2()) |
| return; |
| out.printf(", @%u", child2()->index()); |
| if (!child3()) |
| return; |
| out.printf(", @%u", child3()->index()); |
| } |
| |
| NodeOrigin origin; |
| |
| // References to up to 3 children, or links to a variable length set of children. |
| AdjacencyList children; |
| |
| private: |
| friend class B3::SparseCollection<Node>; |
| |
| unsigned m_index { std::numeric_limits<unsigned>::max() }; |
| unsigned m_op : 10; // real type is NodeType |
| unsigned m_flags : 21; |
| // The virtual register number (spill location) associated with this . |
| VirtualRegister m_virtualRegister; |
| // The number of uses of the result of this operation (+1 for 'must generate' nodes, which have side-effects). |
| unsigned m_refCount; |
| // The prediction ascribed to this node after propagation. |
| SpeculatedType m_prediction { SpecNone }; |
| // Immediate values, accesses type-checked via accessors above. |
| struct OpInfoWrapper { |
| OpInfoWrapper() |
| { |
| u.int64 = 0; |
| } |
| OpInfoWrapper(uint32_t intValue) |
| { |
| u.int64 = 0; |
| u.int32 = intValue; |
| } |
| OpInfoWrapper(uint64_t intValue) |
| { |
| u.int64 = intValue; |
| } |
| OpInfoWrapper(void* pointer) |
| { |
| u.int64 = 0; |
| u.pointer = pointer; |
| } |
| OpInfoWrapper(const void* constPointer) |
| { |
| u.int64 = 0; |
| u.constPointer = constPointer; |
| } |
| OpInfoWrapper(RegisteredStructure structure) |
| { |
| u.int64 = 0; |
| u.pointer = bitwise_cast<void*>(structure); |
| } |
| OpInfoWrapper& operator=(uint32_t int32) |
| { |
| u.int64 = 0; |
| u.int32 = int32; |
| return *this; |
| } |
| OpInfoWrapper& operator=(int32_t int32) |
| { |
| u.int64 = 0; |
| u.int32 = int32; |
| return *this; |
| } |
| OpInfoWrapper& operator=(uint64_t int64) |
| { |
| u.int64 = int64; |
| return *this; |
| } |
| OpInfoWrapper& operator=(void* pointer) |
| { |
| u.int64 = 0; |
| u.pointer = pointer; |
| return *this; |
| } |
| OpInfoWrapper& operator=(const void* constPointer) |
| { |
| u.int64 = 0; |
| u.constPointer = constPointer; |
| return *this; |
| } |
| OpInfoWrapper& operator=(RegisteredStructure structure) |
| { |
| u.int64 = 0; |
| u.pointer = bitwise_cast<void*>(structure); |
| return *this; |
| } |
| OpInfoWrapper& operator=(NewArrayBufferData newArrayBufferData) |
| { |
| u.int64 = bitwise_cast<uint64_t>(newArrayBufferData); |
| return *this; |
| } |
| template <typename T> |
| ALWAYS_INLINE auto as() const -> typename std::enable_if<std::is_pointer<T>::value && !std::is_const<typename std::remove_pointer<T>::type>::value, T>::type |
| { |
| return static_cast<T>(u.pointer); |
| } |
| template <typename T> |
| ALWAYS_INLINE auto as() const -> typename std::enable_if<std::is_pointer<T>::value && std::is_const<typename std::remove_pointer<T>::type>::value, T>::type |
| { |
| return static_cast<T>(u.constPointer); |
| } |
| template <typename T> |
| ALWAYS_INLINE auto as() const -> typename std::enable_if<(std::is_integral<T>::value || std::is_enum<T>::value) && sizeof(T) <= 4, T>::type |
| { |
| return static_cast<T>(u.int32); |
| } |
| template <typename T> |
| ALWAYS_INLINE auto as() const -> typename std::enable_if<(std::is_integral<T>::value || std::is_enum<T>::value) && sizeof(T) == 8, T>::type |
| { |
| return static_cast<T>(u.int64); |
| } |
| ALWAYS_INLINE RegisteredStructure asRegisteredStructure() const |
| { |
| return bitwise_cast<RegisteredStructure>(u.pointer); |
| } |
| ALWAYS_INLINE NewArrayBufferData asNewArrayBufferData() const |
| { |
| return bitwise_cast<NewArrayBufferData>(u.int64); |
| } |
| |
| union { |
| uint32_t int32; |
| uint64_t int64; |
| void* pointer; |
| const void* constPointer; |
| } u; |
| }; |
| OpInfoWrapper m_opInfo; |
| OpInfoWrapper m_opInfo2; |
| |
| // Miscellaneous data that is usually meaningless, but can hold some analysis results |
| // if you ask right. For example, if you do Graph::initializeNodeOwners(), Node::owner |
| // will tell you which basic block a node belongs to. You cannot rely on this persisting |
| // across transformations unless you do the maintenance work yourself. Other phases use |
| // Node::replacement, but they do so manually: first you do Graph::clearReplacements() |
| // and then you set, and use, replacement's yourself. Same thing for epoch. |
| // |
| // Bottom line: don't use these fields unless you initialize them yourself, or by |
| // calling some appropriate methods that initialize them the way you want. Otherwise, |
| // these fields are meaningless. |
| private: |
| union { |
| Node* replacement; |
| unsigned epoch; |
| } m_misc; |
| public: |
| BasicBlock* owner; |
| }; |
| |
| // Uncomment this to log NodeSet operations. |
| // typedef LoggingHashSet<Node::HashSetTemplateInstantiationString, Node*> NodeSet; |
| typedef HashSet<Node*> NodeSet; |
| |
| struct NodeComparator { |
| template<typename NodePtrType> |
| bool operator()(NodePtrType a, NodePtrType b) const |
| { |
| return a->index() < b->index(); |
| } |
| }; |
| |
| template<typename T> |
| CString nodeListDump(const T& nodeList) |
| { |
| return sortedListDump(nodeList, NodeComparator()); |
| } |
| |
| template<typename T> |
| CString nodeMapDump(const T& nodeMap, DumpContext* context = 0) |
| { |
| Vector<typename T::KeyType> keys; |
| for ( |
| typename T::const_iterator iter = nodeMap.begin(); |
| iter != nodeMap.end(); ++iter) |
| keys.append(iter->key); |
| std::sort(keys.begin(), keys.end(), NodeComparator()); |
| StringPrintStream out; |
| CommaPrinter comma; |
| for(unsigned i = 0; i < keys.size(); ++i) |
| out.print(comma, keys[i], "=>", inContext(nodeMap.get(keys[i]), context)); |
| return out.toCString(); |
| } |
| |
| template<typename T> |
| CString nodeValuePairListDump(const T& nodeValuePairList, DumpContext* context = 0) |
| { |
| using V = typename T::ValueType; |
| T sortedList = nodeValuePairList; |
| std::sort(sortedList.begin(), sortedList.end(), [](const V& a, const V& b) { |
| return NodeComparator()(a.node, b.node); |
| }); |
| |
| StringPrintStream out; |
| CommaPrinter comma; |
| for (const auto& pair : sortedList) |
| out.print(comma, pair.node, "=>", inContext(pair.value, context)); |
| return out.toCString(); |
| } |
| |
| } } // namespace JSC::DFG |
| |
| namespace WTF { |
| |
| void printInternal(PrintStream&, JSC::DFG::SwitchKind); |
| void printInternal(PrintStream&, JSC::DFG::Node*); |
| |
| inline JSC::DFG::Node* inContext(JSC::DFG::Node* node, JSC::DumpContext*) { return node; } |
| |
| template<> |
| struct LoggingHashKeyTraits<JSC::DFG::Node*> { |
| static void print(PrintStream& out, JSC::DFG::Node* key) |
| { |
| out.print("bitwise_cast<::JSC::DFG::Node*>(", RawPointer(key), "lu)"); |
| } |
| }; |
| |
| } // namespace WTF |
| |
| using WTF::inContext; |
| |
| #endif |