|  | /* | 
|  | * Copyright (C) 2012-2019 Apple Inc. All rights reserved. | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions | 
|  | * are met: | 
|  | * 1. Redistributions of source code must retain the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer. | 
|  | * 2. Redistributions in binary form must reproduce the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer in the | 
|  | *    documentation and/or other materials provided with the distribution. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | 
|  | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 
|  | * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR | 
|  | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 
|  | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 
|  | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 
|  | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 
|  | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "DFGConstantFoldingPhase.h" | 
|  |  | 
|  | #if ENABLE(DFG_JIT) | 
|  |  | 
|  | #include "BuiltinNames.h" | 
|  | #include "DFGAbstractInterpreterInlines.h" | 
|  | #include "DFGArgumentsUtilities.h" | 
|  | #include "DFGBasicBlockInlines.h" | 
|  | #include "DFGGraph.h" | 
|  | #include "DFGInPlaceAbstractState.h" | 
|  | #include "DFGInsertionSet.h" | 
|  | #include "DFGPhase.h" | 
|  | #include "GetByStatus.h" | 
|  | #include "JSCInlines.h" | 
|  | #include "PutByIdStatus.h" | 
|  | #include "StructureCache.h" | 
|  |  | 
|  | namespace JSC { namespace DFG { | 
|  |  | 
|  | class ConstantFoldingPhase : public Phase { | 
|  | public: | 
|  | ConstantFoldingPhase(Graph& graph) | 
|  | : Phase(graph, "constant folding") | 
|  | , m_state(graph) | 
|  | , m_interpreter(graph, m_state) | 
|  | , m_insertionSet(graph) | 
|  | { | 
|  | } | 
|  |  | 
|  | bool run() | 
|  | { | 
|  | bool changed = false; | 
|  |  | 
|  | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { | 
|  | if (block->cfaThinksShouldTryConstantFolding) | 
|  | changed |= foldConstants(block); | 
|  | } | 
|  |  | 
|  | if (changed && m_graph.m_form == SSA) { | 
|  | // It's now possible that we have Upsilons pointed at JSConstants. Fix that. | 
|  | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) | 
|  | fixUpsilons(block); | 
|  | } | 
|  |  | 
|  | if (m_graph.m_form == SSA) { | 
|  | // It's now possible to simplify basic blocks by placing an Unreachable terminator right | 
|  | // after anything that invalidates AI. | 
|  | bool didClipBlock = false; | 
|  | Vector<Node*> nodesToDelete; | 
|  | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { | 
|  | m_state.beginBasicBlock(block); | 
|  | for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { | 
|  | if (block->at(nodeIndex)->isTerminal()) { | 
|  | // It's possible that we have something after the terminal. It could be a | 
|  | // no-op Check node, for example. We don't want the logic below to turn that | 
|  | // node into Unreachable, since then we'd have two terminators. | 
|  | break; | 
|  | } | 
|  | if (!m_state.isValid()) { | 
|  | NodeOrigin origin = block->at(nodeIndex)->origin; | 
|  | for (unsigned killIndex = nodeIndex; killIndex < block->size(); ++killIndex) | 
|  | nodesToDelete.append(block->at(killIndex)); | 
|  | block->resize(nodeIndex); | 
|  | block->appendNode(m_graph, SpecNone, Unreachable, origin); | 
|  | didClipBlock = true; | 
|  | break; | 
|  | } | 
|  | m_interpreter.execute(nodeIndex); | 
|  | } | 
|  | m_state.reset(); | 
|  | } | 
|  |  | 
|  | if (didClipBlock) { | 
|  | changed = true; | 
|  |  | 
|  | m_graph.invalidateNodeLiveness(); | 
|  |  | 
|  | for (Node* node : nodesToDelete) | 
|  | m_graph.deleteNode(node); | 
|  |  | 
|  | m_graph.invalidateCFG(); | 
|  | m_graph.resetReachability(); | 
|  | m_graph.killUnreachableBlocks(); | 
|  | } | 
|  | } | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool foldConstants(BasicBlock* block) | 
|  | { | 
|  | bool changed = false; | 
|  | m_state.beginBasicBlock(block); | 
|  | for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { | 
|  | if (!m_state.isValid()) | 
|  | break; | 
|  |  | 
|  | Node* node = block->at(indexInBlock); | 
|  |  | 
|  | bool alreadyHandled = false; | 
|  | bool eliminated = false; | 
|  |  | 
|  | switch (node->op()) { | 
|  | case BooleanToNumber: { | 
|  | if (node->child1().useKind() == UntypedUse | 
|  | && !m_interpreter.needsTypeCheck(node->child1(), SpecBoolean)) | 
|  | node->child1().setUseKind(BooleanUse); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CompareEq: { | 
|  | // FIXME: We should add back the broken folding phase here for comparisions where we prove at least one side has type SpecOther. | 
|  | // See: https://bugs.webkit.org/show_bug.cgi?id=174844 | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CompareStrictEq: | 
|  | case SameValue: { | 
|  | if (node->isBinaryUseKind(UntypedUse)) { | 
|  | JSValue child1Constant = m_state.forNode(node->child1().node()).value(); | 
|  | JSValue child2Constant = m_state.forNode(node->child2().node()).value(); | 
|  |  | 
|  | auto isNonStringAndNonBigIntCellConstant = [] (JSValue value) { | 
|  | return value && value.isCell() && !value.isString() && !value.isHeapBigInt(); | 
|  | }; | 
|  |  | 
|  | if (isNonStringAndNonBigIntCellConstant(child1Constant)) { | 
|  | node->convertToCompareEqPtr(m_graph.freezeStrong(child1Constant.asCell()), node->child2()); | 
|  | changed = true; | 
|  | } else if (isNonStringAndNonBigIntCellConstant(child2Constant)) { | 
|  | node->convertToCompareEqPtr(m_graph.freezeStrong(child2Constant.asCell()), node->child1()); | 
|  | changed = true; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckStructureOrEmpty: { | 
|  | const AbstractValue& value = m_state.forNode(node->child1()); | 
|  | if (value.m_type & SpecEmpty) | 
|  | break; | 
|  | node->convertCheckStructureOrEmptyToCheckStructure(); | 
|  | changed = true; | 
|  | FALLTHROUGH; | 
|  | } | 
|  | case CheckStructure: | 
|  | case ArrayifyToStructure: { | 
|  | AbstractValue& value = m_state.forNode(node->child1()); | 
|  | RegisteredStructureSet set; | 
|  | if (node->op() == ArrayifyToStructure) { | 
|  | set = node->structure(); | 
|  | ASSERT(!isCopyOnWrite(node->structure()->indexingMode())); | 
|  | } | 
|  | else { | 
|  | set = node->structureSet(); | 
|  | if ((SpecCellCheck & SpecEmpty) && node->child1().useKind() == CellUse && m_state.forNode(node->child1()).m_type & SpecEmpty) { | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, AssertNotEmpty, node->origin, Edge(node->child1().node(), UntypedUse)); | 
|  | } | 
|  | } | 
|  | if (value.m_structure.isSubsetOf(set)) { | 
|  | m_interpreter.execute(indexInBlock); // Catch the fact that we may filter on cell. | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckJSCast: { | 
|  | JSValue constant = m_state.forNode(node->child1()).value(); | 
|  | if (constant) { | 
|  | if (constant.isCell() && constant.asCell()->inherits(m_graph.m_vm, node->classInfo())) { | 
|  | m_interpreter.execute(indexInBlock); | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | AbstractValue& value = m_state.forNode(node->child1()); | 
|  |  | 
|  | if (value.m_structure.isSubClassOf(node->classInfo())) { | 
|  | m_interpreter.execute(indexInBlock); | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckNotJSCast: { | 
|  | JSValue constant = m_state.forNode(node->child1()).value(); | 
|  | if (constant) { | 
|  | if (constant.isCell() && !constant.asCell()->inherits(m_graph.m_vm, node->classInfo())) { | 
|  | m_interpreter.execute(indexInBlock); | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | AbstractValue& value = m_state.forNode(node->child1()); | 
|  |  | 
|  | if (value.m_structure.isNotSubClassOf(node->classInfo())) { | 
|  | m_interpreter.execute(indexInBlock); | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case GetIndexedPropertyStorage: { | 
|  | JSArrayBufferView* view = m_graph.tryGetFoldableView( | 
|  | m_state.forNode(node->child1()).m_value, node->arrayMode()); | 
|  | if (!view) | 
|  | break; | 
|  |  | 
|  | if (view->mode() == FastTypedArray) { | 
|  | // FIXME: It would be awesome to be able to fold the property storage for | 
|  | // these GC-allocated typed arrays. For now it doesn't matter because the | 
|  | // most common use-cases for constant typed arrays involve large arrays with | 
|  | // aliased buffer views. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=125425 | 
|  | break; | 
|  | } | 
|  |  | 
|  | m_interpreter.execute(indexInBlock); | 
|  | eliminated = true; | 
|  |  | 
|  | m_insertionSet.insertCheck(indexInBlock, node->origin, node->children); | 
|  | node->convertToConstantStoragePointer(view->vector()); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckStructureImmediate: { | 
|  | AbstractValue& value = m_state.forNode(node->child1()); | 
|  | const RegisteredStructureSet& set = node->structureSet(); | 
|  |  | 
|  | if (value.value()) { | 
|  | if (Structure* structure = jsDynamicCast<Structure*>(m_graph.m_vm, value.value())) { | 
|  | if (set.contains(m_graph.registerStructure(structure))) { | 
|  | m_interpreter.execute(indexInBlock); | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (PhiChildren* phiChildren = m_interpreter.phiChildren()) { | 
|  | bool allGood = true; | 
|  | phiChildren->forAllTransitiveIncomingValues( | 
|  | node, | 
|  | [&] (Node* incoming) { | 
|  | if (Structure* structure = incoming->dynamicCastConstant<Structure*>(m_graph.m_vm)) { | 
|  | if (set.contains(m_graph.registerStructure(structure))) | 
|  | return; | 
|  | } | 
|  | allGood = false; | 
|  | }); | 
|  | if (allGood) { | 
|  | m_interpreter.execute(indexInBlock); | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckArrayOrEmpty: { | 
|  | const AbstractValue& value = m_state.forNode(node->child1()); | 
|  | if (!(value.m_type & SpecEmpty)) { | 
|  | node->convertCheckArrayOrEmptyToCheckArray(); | 
|  | changed = true; | 
|  | } | 
|  | // Even if the input includes SpecEmpty, we can fall through to CheckArray and remove the node. | 
|  | // CheckArrayOrEmpty can be removed when arrayMode meets the requirement. In that case, CellUse's | 
|  | // check just remains, and it works as CheckArrayOrEmpty without ArrayMode checking. | 
|  | ASSERT(typeFilterFor(node->child1().useKind()) & SpecEmpty); | 
|  | FALLTHROUGH; | 
|  | } | 
|  |  | 
|  | case CheckArray: | 
|  | case Arrayify: { | 
|  | if (!node->arrayMode().alreadyChecked(m_graph, node, m_state.forNode(node->child1()))) | 
|  | break; | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case PutStructure: { | 
|  | if (m_state.forNode(node->child1()).m_structure.onlyStructure() != node->transition()->next) | 
|  | break; | 
|  |  | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckIsConstant: { | 
|  | if (m_state.forNode(node->child1()).value() != node->constant()->value()) | 
|  | break; | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case AssertNotEmpty: | 
|  | case CheckNotEmpty: { | 
|  | if (m_state.forNode(node->child1()).m_type & SpecEmpty) | 
|  | break; | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckIdent: { | 
|  | UniquedStringImpl* uid = node->uidOperand(); | 
|  | const UniquedStringImpl* constantUid = nullptr; | 
|  |  | 
|  | JSValue childConstant = m_state.forNode(node->child1()).value(); | 
|  | if (childConstant) { | 
|  | if (childConstant.isString()) { | 
|  | if (const auto* impl = asString(childConstant)->tryGetValueImpl()) { | 
|  | // Edge filtering requires that a value here should be StringIdent. | 
|  | // However, a constant value propagated in DFG is not filtered. | 
|  | // So here, we check the propagated value is actually an atomic string. | 
|  | // And if it's not, we just ignore. | 
|  | if (impl->isAtom()) | 
|  | constantUid = static_cast<const UniquedStringImpl*>(impl); | 
|  | } | 
|  | } else if (childConstant.isSymbol()) { | 
|  | Symbol* symbol = jsCast<Symbol*>(childConstant); | 
|  | constantUid = &symbol->uid(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (constantUid == uid) { | 
|  | node->remove(m_graph); | 
|  | eliminated = true; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckInBounds: { | 
|  | JSValue left = m_state.forNode(node->child1()).value(); | 
|  | JSValue right = m_state.forNode(node->child2()).value(); | 
|  | if (left && right && left.isInt32() && right.isInt32() | 
|  | && static_cast<uint32_t>(left.asInt32()) < static_cast<uint32_t>(right.asInt32())) { | 
|  |  | 
|  | Node* zero = m_insertionSet.insertConstant(indexInBlock, node->origin, jsNumber(0)); | 
|  | node->convertToIdentityOn(zero); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | case GetMyArgumentByVal: | 
|  | case GetMyArgumentByValOutOfBounds: { | 
|  | JSValue indexValue = m_state.forNode(node->child2()).value(); | 
|  | if (!indexValue || !indexValue.isUInt32()) | 
|  | break; | 
|  |  | 
|  | Checked<unsigned, RecordOverflow> checkedIndex = indexValue.asUInt32(); | 
|  | checkedIndex += node->numberOfArgumentsToSkip(); | 
|  | if (checkedIndex.hasOverflowed()) | 
|  | break; | 
|  |  | 
|  | unsigned index = checkedIndex.unsafeGet(); | 
|  | Node* arguments = node->child1().node(); | 
|  | InlineCallFrame* inlineCallFrame = arguments->origin.semantic.inlineCallFrame(); | 
|  |  | 
|  | // Don't try to do anything if the index is known to be outside our static bounds. Note | 
|  | // that our static bounds are usually strictly larger than the dynamic bounds. The | 
|  | // exception is something like this, assuming foo() is not inlined: | 
|  | // | 
|  | // function foo() { return arguments[5]; } | 
|  | // | 
|  | // Here the static bound on number of arguments is 0, and we're accessing index 5. We | 
|  | // will not strength-reduce this to GetStack because GetStack is otherwise assumed by the | 
|  | // compiler to access those variables that are statically accounted for; for example if | 
|  | // we emitted a GetStack on arg6 we would have out-of-bounds access crashes anywhere that | 
|  | // uses an Operands<> map. There is not much cost to continuing to use a | 
|  | // GetMyArgumentByVal in such statically-out-of-bounds accesses; we just lose CFA unless | 
|  | // GCSE removes the access entirely. | 
|  | if (inlineCallFrame) { | 
|  | if (index >= static_cast<unsigned>(inlineCallFrame->argumentCountIncludingThis - 1)) | 
|  | break; | 
|  | } else { | 
|  | if (index >= m_state.numberOfArguments() - 1) | 
|  | break; | 
|  | } | 
|  |  | 
|  | m_interpreter.execute(indexInBlock); // Push CFA over this node after we get the state before. | 
|  |  | 
|  | StackAccessData* data; | 
|  | if (inlineCallFrame) { | 
|  | data = m_graph.m_stackAccessData.add( | 
|  | VirtualRegister( | 
|  | inlineCallFrame->stackOffset + | 
|  | CallFrame::argumentOffset(index)), | 
|  | FlushedJSValue); | 
|  | } else { | 
|  | data = m_graph.m_stackAccessData.add( | 
|  | virtualRegisterForArgumentIncludingThis(index + 1), FlushedJSValue); | 
|  | } | 
|  |  | 
|  | if (inlineCallFrame && !inlineCallFrame->isVarargs() && index < static_cast<unsigned>(inlineCallFrame->argumentCountIncludingThis - 1)) { | 
|  | node->convertToGetStack(data); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (node->op() == GetMyArgumentByValOutOfBounds) | 
|  | break; | 
|  |  | 
|  | Node* length = emitCodeToGetArgumentsArrayLength( | 
|  | m_insertionSet, arguments, indexInBlock, node->origin); | 
|  | Node* check = m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, CheckInBounds, node->origin, | 
|  | node->child2(), Edge(length, Int32Use)); | 
|  | node->convertToGetStack(data); | 
|  | node->child1() = Edge(check, UntypedUse); | 
|  | eliminated = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case MultiGetByOffset: { | 
|  | Edge baseEdge = node->child1(); | 
|  | Node* base = baseEdge.node(); | 
|  | MultiGetByOffsetData& data = node->multiGetByOffsetData(); | 
|  |  | 
|  | // First prune the variants, then check if the MultiGetByOffset can be | 
|  | // strength-reduced to a GetByOffset. | 
|  |  | 
|  | AbstractValue baseValue = m_state.forNode(base); | 
|  |  | 
|  | m_interpreter.execute(indexInBlock); // Push CFA over this node after we get the state before. | 
|  | alreadyHandled = true; // Don't allow the default constant folder to do things to this. | 
|  |  | 
|  | for (unsigned i = 0; i < data.cases.size(); ++i) { | 
|  | MultiGetByOffsetCase& getCase = data.cases[i]; | 
|  | getCase.set().filter(baseValue); | 
|  | if (getCase.set().isEmpty()) { | 
|  | data.cases[i--] = data.cases.last(); | 
|  | data.cases.removeLast(); | 
|  | changed = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (data.cases.size() != 1) | 
|  | break; | 
|  |  | 
|  | emitGetByOffset(indexInBlock, node, baseValue, data.cases[0], data.identifierNumber); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case MultiPutByOffset: { | 
|  | Edge baseEdge = node->child1(); | 
|  | Node* base = baseEdge.node(); | 
|  | MultiPutByOffsetData& data = node->multiPutByOffsetData(); | 
|  |  | 
|  | AbstractValue baseValue = m_state.forNode(base); | 
|  |  | 
|  | m_interpreter.execute(indexInBlock); // Push CFA over this node after we get the state before. | 
|  | alreadyHandled = true; // Don't allow the default constant folder to do things to this. | 
|  |  | 
|  |  | 
|  | for (unsigned i = 0; i < data.variants.size(); ++i) { | 
|  | PutByIdVariant& variant = data.variants[i]; | 
|  | variant.oldStructure().genericFilter([&] (Structure* structure) -> bool { | 
|  | return baseValue.contains(m_graph.registerStructure(structure)); | 
|  | }); | 
|  |  | 
|  | if (variant.oldStructure().isEmpty()) { | 
|  | data.variants[i--] = data.variants.last(); | 
|  | data.variants.removeLast(); | 
|  | changed = true; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (variant.kind() == PutByIdVariant::Transition | 
|  | && variant.oldStructure().onlyStructure() == variant.newStructure()) { | 
|  | variant = PutByIdVariant::replace( | 
|  | variant.oldStructure(), | 
|  | variant.offset()); | 
|  | changed = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (data.variants.size() != 1) | 
|  | break; | 
|  |  | 
|  | emitPutByOffset( | 
|  | indexInBlock, node, baseValue, data.variants[0], data.identifierNumber); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case MultiDeleteByOffset: { | 
|  | Edge baseEdge = node->child1(); | 
|  | Node* base = baseEdge.node(); | 
|  | MultiDeleteByOffsetData& data = node->multiDeleteByOffsetData(); | 
|  |  | 
|  | AbstractValue baseValue = m_state.forNode(base); | 
|  |  | 
|  | m_interpreter.execute(indexInBlock); // Push CFA over this node after we get the state before. | 
|  | alreadyHandled = true; // Don't allow the default constant folder to do things to this. | 
|  |  | 
|  | for (unsigned i = 0; i < data.variants.size(); ++i) { | 
|  | DeleteByIdVariant& variant = data.variants[i]; | 
|  |  | 
|  | if (!baseValue.contains(m_graph.registerStructure(variant.oldStructure()))) { | 
|  | data.variants[i--] = data.variants.last(); | 
|  | data.variants.removeLast(); | 
|  | changed = true; | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (data.variants.size() != 1) | 
|  | break; | 
|  |  | 
|  | emitDeleteByOffset( | 
|  | indexInBlock, node, baseValue, data.variants[0], data.identifierNumber); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case MatchStructure: { | 
|  | Edge baseEdge = node->child1(); | 
|  | Node* base = baseEdge.node(); | 
|  | MatchStructureData& data = node->matchStructureData(); | 
|  |  | 
|  | AbstractValue baseValue = m_state.forNode(base); | 
|  |  | 
|  | m_interpreter.execute(indexInBlock); // Push CFA over this node after we get the state before. | 
|  | alreadyHandled = true; // Don't allow the default constant folder to do things to this. | 
|  |  | 
|  | BooleanLattice result = BooleanLattice::Bottom; | 
|  | for (unsigned i = 0; i < data.variants.size(); ++i) { | 
|  | if (!baseValue.contains(data.variants[i].structure)) { | 
|  | data.variants[i--] = data.variants.last(); | 
|  | data.variants.removeLast(); | 
|  | changed = true; | 
|  | continue; | 
|  | } | 
|  | result = leastUpperBoundOfBooleanLattices( | 
|  | result, | 
|  | data.variants[i].result ? BooleanLattice::True : BooleanLattice::False); | 
|  | } | 
|  |  | 
|  | if (result == BooleanLattice::False || result == BooleanLattice::True) { | 
|  | RegisteredStructureSet structureSet; | 
|  | for (MatchStructureVariant& variant : data.variants) | 
|  | structureSet.add(variant.structure); | 
|  | addBaseCheck(indexInBlock, node, baseValue, structureSet); | 
|  | m_graph.convertToConstant( | 
|  | node, m_graph.freeze(jsBoolean(result == BooleanLattice::True))); | 
|  | changed = true; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case GetByIdDirect: | 
|  | case GetByIdDirectFlush: | 
|  | case GetById: | 
|  | case GetByIdFlush: | 
|  | case GetPrivateNameById: { | 
|  | Edge childEdge = node->child1(); | 
|  | Node* child = childEdge.node(); | 
|  | UniquedStringImpl* uid = node->cacheableIdentifier().uid(); | 
|  |  | 
|  | AbstractValue baseValue = m_state.forNode(child); | 
|  |  | 
|  | m_interpreter.execute(indexInBlock); // Push CFA over this node after we get the state before. | 
|  | alreadyHandled = true; // Don't allow the default constant folder to do things to this. | 
|  |  | 
|  | if (!Options::useAccessInlining()) | 
|  | break; | 
|  |  | 
|  | if (!baseValue.m_structure.isFinite() | 
|  | || (node->child1().useKind() == UntypedUse || (baseValue.m_type & ~SpecCell))) | 
|  | break; | 
|  |  | 
|  | GetByStatus status = GetByStatus::computeFor(baseValue.m_structure.toStructureSet(), uid); | 
|  | if (!status.isSimple()) | 
|  | break; | 
|  |  | 
|  | for (unsigned i = status.numVariants(); i--;) { | 
|  | if (!status[i].conditionSet().isEmpty()) { | 
|  | // FIXME: We could handle prototype cases. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=110386 | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | auto addFilterStatus = [&] () { | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, FilterGetByStatus, node->origin, | 
|  | OpInfo(m_graph.m_plan.recordedStatuses().addGetByStatus(node->origin.semantic, status)), | 
|  | Edge(child)); | 
|  | }; | 
|  |  | 
|  | if (status.numVariants() == 1) { | 
|  | unsigned identifierNumber = m_graph.identifiers().ensure(uid); | 
|  | addFilterStatus(); | 
|  | emitGetByOffset(indexInBlock, node, baseValue, status[0], identifierNumber); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!m_graph.m_plan.isFTL()) | 
|  | break; | 
|  |  | 
|  | unsigned identifierNumber = m_graph.identifiers().ensure(uid); | 
|  | addFilterStatus(); | 
|  | MultiGetByOffsetData* data = m_graph.m_multiGetByOffsetData.add(); | 
|  | for (const GetByIdVariant& variant : status.variants()) { | 
|  | data->cases.append( | 
|  | MultiGetByOffsetCase( | 
|  | *m_graph.addStructureSet(variant.structureSet()), | 
|  | GetByOffsetMethod::load(variant.offset()))); | 
|  | } | 
|  | data->identifierNumber = identifierNumber; | 
|  | node->convertToMultiGetByOffset(data); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case PutPrivateNameById: { | 
|  | bool isDirect = true; | 
|  | tryFoldAsPutByOffset(node, indexInBlock, node->child1(), node->child2(), isDirect, node->privateFieldPutKind(), changed, alreadyHandled); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case PutById: | 
|  | case PutByIdDirect: | 
|  | case PutByIdFlush: { | 
|  | bool isDirect = node->op() == PutByIdDirect; | 
|  | tryFoldAsPutByOffset(node, indexInBlock, node->child1(), node->child2(), isDirect, PrivateFieldPutKind::none(), changed, alreadyHandled); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case InByVal: { | 
|  | AbstractValue& property = m_state.forNode(node->child2()); | 
|  | if (JSValue constant = property.value()) { | 
|  | if (constant.isString()) { | 
|  | JSString* string = asString(constant); | 
|  | if (CacheableIdentifier::isCacheableIdentifierCell(string)) { | 
|  | const StringImpl* impl = string->tryGetValueImpl(); | 
|  | RELEASE_ASSERT(impl); | 
|  | m_graph.freezeStrong(string); | 
|  | m_graph.identifiers().ensure(const_cast<UniquedStringImpl*>(static_cast<const UniquedStringImpl*>(impl))); | 
|  | node->convertToInById(CacheableIdentifier::createFromCell(string)); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ToPrimitive: { | 
|  | if (m_state.forNode(node->child1()).m_type & ~(SpecFullNumber | SpecBoolean | SpecString | SpecSymbol | SpecBigInt)) | 
|  | break; | 
|  |  | 
|  | node->convertToIdentity(); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ToPropertyKey: { | 
|  | if (m_state.forNode(node->child1()).m_type & ~(SpecString | SpecSymbol)) | 
|  | break; | 
|  |  | 
|  | node->convertToIdentity(); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ToThis: { | 
|  | ToThisResult result = isToThisAnIdentity(m_graph.m_vm, node->ecmaMode(), m_state.forNode(node->child1())); | 
|  | if (result == ToThisResult::Identity) { | 
|  | node->convertToIdentity(); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | if (result == ToThisResult::GlobalThis) { | 
|  | node->convertToGetGlobalThis(); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CreateThis: { | 
|  | if (JSValue base = m_state.forNode(node->child1()).m_value) { | 
|  | if (auto* function = jsDynamicCast<JSFunction*>(m_graph.m_vm, base)) { | 
|  | if (FunctionRareData* rareData = function->rareData()) { | 
|  | if (rareData->allocationProfileWatchpointSet().isStillValid()) { | 
|  | Structure* structure = rareData->objectAllocationStructure(); | 
|  | JSObject* prototype = rareData->objectAllocationPrototype(); | 
|  | if (structure | 
|  | && (structure->hasMonoProto() || prototype) | 
|  | && rareData->allocationProfileWatchpointSet().isStillValid()) { | 
|  |  | 
|  | m_graph.freeze(rareData); | 
|  | m_graph.watchpoints().addLazily(rareData->allocationProfileWatchpointSet()); | 
|  | node->convertToNewObject(m_graph.registerStructure(structure)); | 
|  |  | 
|  | if (structure->hasPolyProto()) { | 
|  | StorageAccessData* data = m_graph.m_storageAccessData.add(); | 
|  | data->offset = knownPolyProtoOffset; | 
|  | data->identifierNumber = m_graph.identifiers().ensure(m_graph.m_vm.propertyNames->builtinNames().polyProtoName().impl()); | 
|  | NodeOrigin origin = node->origin.withInvalidExit(); | 
|  | Node* prototypeNode = m_insertionSet.insertConstant( | 
|  | indexInBlock + 1, origin, m_graph.freeze(prototype)); | 
|  |  | 
|  | ASSERT(isInlineOffset(knownPolyProtoOffset)); | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock + 1, SpecNone, PutByOffset, origin, OpInfo(data), | 
|  | Edge(node, KnownCellUse), Edge(node, KnownCellUse), Edge(prototypeNode, UntypedUse)); | 
|  | } | 
|  | changed = true; | 
|  | break; | 
|  |  | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CreatePromise: { | 
|  | JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic); | 
|  | if (JSValue base = m_state.forNode(node->child1()).m_value) { | 
|  | if (base == (node->isInternalPromise() ? globalObject->internalPromiseConstructor() : globalObject->promiseConstructor())) { | 
|  | node->convertToNewInternalFieldObject(m_graph.registerStructure(node->isInternalPromise() ? globalObject->internalPromiseStructure() : globalObject->promiseStructure())); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | if (auto* function = jsDynamicCast<JSFunction*>(m_graph.m_vm, base)) { | 
|  | if (FunctionRareData* rareData = function->rareData()) { | 
|  | if (rareData->allocationProfileWatchpointSet().isStillValid()) { | 
|  | Structure* structure = rareData->internalFunctionAllocationStructure(); | 
|  | if (structure | 
|  | && structure->classInfo() == (node->isInternalPromise() ? JSInternalPromise::info() : JSPromise::info()) | 
|  | && structure->globalObject() == globalObject | 
|  | && rareData->allocationProfileWatchpointSet().isStillValid()) { | 
|  | m_graph.freeze(rareData); | 
|  | m_graph.watchpoints().addLazily(rareData->allocationProfileWatchpointSet()); | 
|  | node->convertToNewInternalFieldObject(m_graph.registerStructure(structure)); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CreateGenerator: | 
|  | case CreateAsyncGenerator: { | 
|  | auto foldConstant = [&] (NodeType newOp, const ClassInfo* classInfo) { | 
|  | JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic); | 
|  | if (JSValue base = m_state.forNode(node->child1()).m_value) { | 
|  | if (auto* function = jsDynamicCast<JSFunction*>(m_graph.m_vm, base)) { | 
|  | if (FunctionRareData* rareData = function->rareData()) { | 
|  | if (rareData->allocationProfileWatchpointSet().isStillValid()) { | 
|  | Structure* structure = rareData->internalFunctionAllocationStructure(); | 
|  | if (structure | 
|  | && structure->classInfo() == classInfo | 
|  | && structure->globalObject() == globalObject | 
|  | && rareData->allocationProfileWatchpointSet().isStillValid()) { | 
|  | m_graph.freeze(rareData); | 
|  | m_graph.watchpoints().addLazily(rareData->allocationProfileWatchpointSet()); | 
|  | node->convertToNewInternalFieldObjectWithInlineFields(newOp, m_graph.registerStructure(structure)); | 
|  | changed = true; | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | switch (node->op()) { | 
|  | case CreateGenerator: | 
|  | foldConstant(NewGenerator, JSGenerator::info()); | 
|  | break; | 
|  | case CreateAsyncGenerator: | 
|  | foldConstant(NewAsyncGenerator, JSAsyncGenerator::info()); | 
|  | break; | 
|  | default: | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | break; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ObjectCreate: { | 
|  | if (JSValue base = m_state.forNode(node->child1()).m_value) { | 
|  | JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic); | 
|  | Structure* structure = nullptr; | 
|  | if (base.isNull()) | 
|  | structure = globalObject->nullPrototypeObjectStructure(); | 
|  | else if (base.isObject()) | 
|  | structure = globalObject->vm().structureCache.emptyObjectStructureConcurrently(globalObject, base.getObject(), JSFinalObject::defaultInlineCapacity()); | 
|  |  | 
|  | if (structure) { | 
|  | node->convertToNewObject(m_graph.registerStructure(structure)); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ObjectGetOwnPropertyNames: | 
|  | case ObjectKeys: { | 
|  | if (node->child1().useKind() == ObjectUse) { | 
|  | auto& structureSet = m_state.forNode(node->child1()).m_structure; | 
|  | if (structureSet.isFinite() && structureSet.size() == 1) { | 
|  | RegisteredStructure structure = structureSet.onlyStructure(); | 
|  | if (auto* rareData = structure->rareDataConcurrently()) { | 
|  | if (auto* immutableButterfly = rareData->cachedPropertyNamesConcurrently(node->op() == ObjectGetOwnPropertyNames ? CachedPropertyNamesKind::GetOwnPropertyNames : CachedPropertyNamesKind::Keys)) { | 
|  | if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) { | 
|  | node->convertToNewArrayBuffer(m_graph.freeze(immutableButterfly)); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case NewArrayWithSpread: { | 
|  | if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) { | 
|  | BitVector* bitVector = node->bitVector(); | 
|  | if (node->numChildren() == 1 && bitVector->get(0)) { | 
|  | Edge use = m_graph.varArgChild(node, 0); | 
|  | if (use->op() == PhantomSpread) { | 
|  | if (use->child1()->op() == PhantomNewArrayBuffer) { | 
|  | auto* immutableButterfly = use->child1()->castOperand<JSImmutableButterfly*>(); | 
|  | if (hasContiguous(immutableButterfly->indexingType())) { | 
|  | node->convertToNewArrayBuffer(m_graph.freeze(immutableButterfly)); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ToNumber: | 
|  | case CallNumberConstructor: { | 
|  | if (m_state.forNode(node->child1()).m_type & ~SpecBytecodeNumber) | 
|  | break; | 
|  |  | 
|  | node->convertToIdentity(); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ToNumeric: { | 
|  | if (m_state.forNode(node->child1()).m_type & ~(SpecBytecodeNumber | SpecBigInt)) | 
|  | break; | 
|  |  | 
|  | node->convertToIdentity(); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case NormalizeMapKey: { | 
|  | SpeculatedType typesNeedingNormalization = (SpecFullNumber & ~SpecInt32Only) | SpecHeapBigInt; | 
|  | if (m_state.forNode(node->child1()).m_type & typesNeedingNormalization) | 
|  | break; | 
|  |  | 
|  | node->convertToIdentity(); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ParseInt: { | 
|  | AbstractValue& value = m_state.forNode(node->child1()); | 
|  | if (!value.m_type || (value.m_type & ~SpecInt32Only)) | 
|  | break; | 
|  |  | 
|  | JSValue radix; | 
|  | if (!node->child2()) | 
|  | radix = jsNumber(0); | 
|  | else | 
|  | radix = m_state.forNode(node->child2()).m_value; | 
|  |  | 
|  | if (!radix.isNumber()) | 
|  | break; | 
|  |  | 
|  | if (radix.asNumber() == 0 || radix.asNumber() == 10) { | 
|  | node->child2() = Edge(); | 
|  | node->convertToIdentity(); | 
|  | changed = true; | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | case NumberToStringWithRadix: { | 
|  | JSValue radixValue = m_state.forNode(node->child2()).m_value; | 
|  | if (radixValue && radixValue.isInt32()) { | 
|  | int32_t radix = radixValue.asInt32(); | 
|  | if (2 <= radix && radix <= 36) { | 
|  | if (radix == 10) { | 
|  | node->setOpAndDefaultFlags(ToString); | 
|  | node->clearFlags(NodeMustGenerate); | 
|  | node->child2() = Edge(); | 
|  | } else | 
|  | node->convertToNumberToStringWithValidRadixConstant(radix); | 
|  | changed = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case Check: { | 
|  | alreadyHandled = true; | 
|  | m_interpreter.execute(indexInBlock); | 
|  | for (unsigned i = 0; i < AdjacencyList::Size; ++i) { | 
|  | Edge edge = node->children.child(i); | 
|  | if (!edge) | 
|  | break; | 
|  | if (edge.isProved() || edge.willNotHaveCheck()) { | 
|  | node->children.removeEdge(i--); | 
|  | changed = true; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckVarargs: { | 
|  | alreadyHandled = true; | 
|  | m_interpreter.execute(indexInBlock); | 
|  | unsigned targetIndex = 0; | 
|  | for (unsigned i = 0; i < node->numChildren(); ++i) { | 
|  | Edge& edge = m_graph.varArgChild(node, i); | 
|  | if (!edge) | 
|  | continue; | 
|  | if (edge.isProved() || edge.willNotHaveCheck()) { | 
|  | edge = Edge(); | 
|  | changed = true; | 
|  | continue; | 
|  | } | 
|  | Edge& dst = m_graph.varArgChild(node, targetIndex++); | 
|  | std::swap(dst, edge); | 
|  | } | 
|  | node->children.setNumChildren(targetIndex); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case MakeRope: { | 
|  | for (unsigned i = 0; i < AdjacencyList::Size; ++i) { | 
|  | Edge& edge = node->children.child(i); | 
|  | if (!edge) | 
|  | break; | 
|  | JSValue childConstant = m_state.forNode(edge).value(); | 
|  | if (!childConstant) | 
|  | continue; | 
|  | if (!childConstant.isString()) | 
|  | continue; | 
|  | if (asString(childConstant)->length()) | 
|  | continue; | 
|  |  | 
|  | // Don't allow the MakeRope to have zero children. | 
|  | if (!i && !node->child2()) | 
|  | break; | 
|  |  | 
|  | node->children.removeEdge(i--); | 
|  | changed = true; | 
|  | } | 
|  |  | 
|  | if (!node->child2()) { | 
|  | ASSERT(!node->child3()); | 
|  | node->convertToIdentity(); | 
|  | changed = true; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case CheckTypeInfoFlags: { | 
|  | const AbstractValue& abstractValue = m_state.forNode(node->child1()); | 
|  | unsigned bits = node->typeInfoOperand(); | 
|  | ASSERT(bits); | 
|  | if (bits == ImplementsDefaultHasInstance) { | 
|  | if (abstractValue.m_type == SpecFunctionWithDefaultHasInstance) { | 
|  | eliminated = true; | 
|  | node->remove(m_graph); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (JSValue value = abstractValue.value()) { | 
|  | if (value.isCell()) { | 
|  | // This works because if we see a cell here, we know it's fully constructed | 
|  | // and we can read its inline type info flags. These flags don't change over the | 
|  | // object's lifetime. | 
|  | if ((value.asCell()->inlineTypeFlags() & bits) == bits) { | 
|  | eliminated = true; | 
|  | node->remove(m_graph); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (abstractValue.m_structure.isFinite()) { | 
|  | bool ok = true; | 
|  | abstractValue.m_structure.forEach([&] (RegisteredStructure structure) { | 
|  | ok &= (structure->typeInfo().inlineTypeFlags() & bits) == bits; | 
|  | }); | 
|  | if (ok) { | 
|  | eliminated = true; | 
|  | node->remove(m_graph); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | case PhantomNewObject: | 
|  | case PhantomNewFunction: | 
|  | case PhantomNewGeneratorFunction: | 
|  | case PhantomNewAsyncGeneratorFunction: | 
|  | case PhantomNewAsyncFunction: | 
|  | case PhantomNewInternalFieldObject: | 
|  | case PhantomCreateActivation: | 
|  | case PhantomDirectArguments: | 
|  | case PhantomClonedArguments: | 
|  | case PhantomCreateRest: | 
|  | case PhantomSpread: | 
|  | case PhantomNewArrayWithSpread: | 
|  | case PhantomNewArrayBuffer: | 
|  | case PhantomNewRegexp: | 
|  | case BottomValue: | 
|  | alreadyHandled = true; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (eliminated) { | 
|  | changed = true; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (alreadyHandled) | 
|  | continue; | 
|  |  | 
|  | m_interpreter.execute(indexInBlock); | 
|  | if (!m_state.isValid()) { | 
|  | // If we invalidated then we shouldn't attempt to constant-fold. Here's an | 
|  | // example: | 
|  | // | 
|  | //     c: JSConstant(4.2) | 
|  | //     x: ValueToInt32(Check:Int32:@const) | 
|  | // | 
|  | // It would be correct for an analysis to assume that execution cannot | 
|  | // proceed past @x. Therefore, constant-folding @x could be rather bad. But, | 
|  | // the CFA may report that it found a constant even though it also reported | 
|  | // that everything has been invalidated. This will only happen in a couple of | 
|  | // the constant folding cases; most of them are also separately defensive | 
|  | // about such things. | 
|  | break; | 
|  | } | 
|  | if (!node->shouldGenerate() || m_state.didClobber() || node->hasConstant() || !node->result()) | 
|  | continue; | 
|  |  | 
|  | // Interesting fact: this freezing that we do right here may turn an fragile value into | 
|  | // a weak value. See DFGValueStrength.h. | 
|  | FrozenValue* value = m_graph.freeze(m_state.forNode(node).value()); | 
|  | if (!*value) | 
|  | continue; | 
|  |  | 
|  | if (node->op() == GetLocal) { | 
|  | // Need to preserve bytecode liveness in ThreadedCPS form. This wouldn't be necessary | 
|  | // if it wasn't for https://bugs.webkit.org/show_bug.cgi?id=144086. | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, PhantomLocal, node->origin, | 
|  | OpInfo(node->variableAccessData())); | 
|  | m_graph.dethread(); | 
|  | } else | 
|  | m_insertionSet.insertCheck(m_graph, indexInBlock, node); | 
|  | m_graph.convertToConstant(node, value); | 
|  |  | 
|  | changed = true; | 
|  | } | 
|  | m_state.reset(); | 
|  | m_insertionSet.execute(block); | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | void emitGetByOffset(unsigned indexInBlock, Node* node, const AbstractValue& baseValue, const MultiGetByOffsetCase& getCase, unsigned identifierNumber) | 
|  | { | 
|  | // When we get to here we have already emitted all of the requisite checks for everything. | 
|  | // So, we just need to emit what the method object tells us to emit. | 
|  |  | 
|  | addBaseCheck(indexInBlock, node, baseValue, getCase.set()); | 
|  |  | 
|  | GetByOffsetMethod method = getCase.method(); | 
|  |  | 
|  | switch (method.kind()) { | 
|  | case GetByOffsetMethod::Invalid: | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | return; | 
|  |  | 
|  | case GetByOffsetMethod::Constant: | 
|  | m_graph.convertToConstant(node, method.constant()); | 
|  | return; | 
|  |  | 
|  | case GetByOffsetMethod::Load: | 
|  | emitGetByOffset(indexInBlock, node, node->child1(), identifierNumber, method.offset()); | 
|  | return; | 
|  |  | 
|  | case GetByOffsetMethod::LoadFromPrototype: { | 
|  | Node* child = m_insertionSet.insertConstant( | 
|  | indexInBlock, node->origin, method.prototype()); | 
|  | emitGetByOffset( | 
|  | indexInBlock, node, Edge(child, KnownCellUse), identifierNumber, method.offset()); | 
|  | return; | 
|  | } } | 
|  |  | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | } | 
|  |  | 
|  | void emitGetByOffset(unsigned indexInBlock, Node* node, const AbstractValue& baseValue, const GetByIdVariant& variant, unsigned identifierNumber) | 
|  | { | 
|  | Edge childEdge = node->child1(); | 
|  |  | 
|  | addBaseCheck(indexInBlock, node, baseValue, variant.structureSet()); | 
|  |  | 
|  | // We aren't set up to handle prototype stuff. | 
|  | DFG_ASSERT(m_graph, node, variant.conditionSet().isEmpty()); | 
|  |  | 
|  | if (JSValue value = m_graph.tryGetConstantProperty(baseValue.m_value, *m_graph.addStructureSet(variant.structureSet()), variant.offset())) { | 
|  | m_graph.convertToConstant(node, m_graph.freeze(value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | emitGetByOffset(indexInBlock, node, childEdge, identifierNumber, variant.offset()); | 
|  | } | 
|  |  | 
|  | void emitGetByOffset( | 
|  | unsigned indexInBlock, Node* node, Edge childEdge, unsigned identifierNumber, | 
|  | PropertyOffset offset) | 
|  | { | 
|  | childEdge.setUseKind(KnownCellUse); | 
|  |  | 
|  | Edge propertyStorage; | 
|  |  | 
|  | if (isInlineOffset(offset)) | 
|  | propertyStorage = childEdge; | 
|  | else { | 
|  | propertyStorage = Edge(m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, GetButterfly, node->origin, childEdge)); | 
|  | } | 
|  |  | 
|  | StorageAccessData& data = *m_graph.m_storageAccessData.add(); | 
|  | data.offset = offset; | 
|  | data.identifierNumber = identifierNumber; | 
|  |  | 
|  | node->convertToGetByOffset(data, propertyStorage, childEdge); | 
|  | } | 
|  |  | 
|  | void emitPutByOffset(unsigned indexInBlock, Node* node, const AbstractValue& baseValue, const PutByIdVariant& variant, unsigned identifierNumber) | 
|  | { | 
|  | NodeOrigin origin = node->origin; | 
|  | Edge childEdge = node->child1(); | 
|  |  | 
|  | addBaseCheck(indexInBlock, node, baseValue, variant.oldStructure()); | 
|  |  | 
|  | node->child1().setUseKind(KnownCellUse); | 
|  | childEdge.setUseKind(KnownCellUse); | 
|  |  | 
|  | Transition* transition = nullptr; | 
|  | if (variant.kind() == PutByIdVariant::Transition) { | 
|  | transition = m_graph.m_transitions.add( | 
|  | m_graph.registerStructure(variant.oldStructureForTransition()), m_graph.registerStructure(variant.newStructure())); | 
|  | } | 
|  |  | 
|  | Edge propertyStorage; | 
|  |  | 
|  | DFG_ASSERT(m_graph, node, origin.exitOK); | 
|  | bool canExit = true; | 
|  | bool didAllocateStorage = false; | 
|  |  | 
|  | if (isInlineOffset(variant.offset())) | 
|  | propertyStorage = childEdge; | 
|  | else if (!variant.reallocatesStorage()) { | 
|  | propertyStorage = Edge(m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, GetButterfly, origin, childEdge)); | 
|  | } else if (!variant.oldStructureForTransition()->outOfLineCapacity()) { | 
|  | ASSERT(variant.newStructure()->outOfLineCapacity()); | 
|  | ASSERT(!isInlineOffset(variant.offset())); | 
|  | Node* allocatePropertyStorage = m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, AllocatePropertyStorage, | 
|  | origin, OpInfo(transition), childEdge); | 
|  | propertyStorage = Edge(allocatePropertyStorage); | 
|  | didAllocateStorage = true; | 
|  | } else { | 
|  | ASSERT(variant.oldStructureForTransition()->outOfLineCapacity()); | 
|  | ASSERT(variant.newStructure()->outOfLineCapacity() > variant.oldStructureForTransition()->outOfLineCapacity()); | 
|  | ASSERT(!isInlineOffset(variant.offset())); | 
|  |  | 
|  | Node* reallocatePropertyStorage = m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, ReallocatePropertyStorage, origin, | 
|  | OpInfo(transition), childEdge, | 
|  | Edge(m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, GetButterfly, origin, childEdge))); | 
|  | propertyStorage = Edge(reallocatePropertyStorage); | 
|  | didAllocateStorage = true; | 
|  | } | 
|  |  | 
|  | StorageAccessData& data = *m_graph.m_storageAccessData.add(); | 
|  | data.offset = variant.offset(); | 
|  | data.identifierNumber = identifierNumber; | 
|  |  | 
|  | node->convertToPutByOffset(data, propertyStorage, childEdge); | 
|  | node->origin.exitOK = canExit; | 
|  |  | 
|  | if (variant.kind() == PutByIdVariant::Transition) { | 
|  | if (didAllocateStorage) { | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock + 1, SpecNone, NukeStructureAndSetButterfly, | 
|  | origin.withInvalidExit(), childEdge, propertyStorage); | 
|  | } | 
|  |  | 
|  | // FIXME: PutStructure goes last until we fix either | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=142921 or | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=142924. | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock + 1, SpecNone, PutStructure, origin.withInvalidExit(), OpInfo(transition), | 
|  | childEdge); | 
|  | } | 
|  | } | 
|  |  | 
|  | void emitDeleteByOffset(unsigned indexInBlock, Node* node, const AbstractValue& baseValue, const DeleteByIdVariant& variant, unsigned identifierNumber) | 
|  | { | 
|  | NodeOrigin origin = node->origin; | 
|  | DFG_ASSERT(m_graph, node, origin.exitOK); | 
|  | addBaseCheck(indexInBlock, node, baseValue, m_graph.registerStructure(variant.oldStructure())); | 
|  | node->child1().setUseKind(KnownCellUse); | 
|  |  | 
|  | if (!variant.newStructure()) { | 
|  | m_graph.convertToConstant(node, jsBoolean(variant.result())); | 
|  | node->origin = node->origin.withInvalidExit(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | Transition* transition = m_graph.m_transitions.add( | 
|  | m_graph.registerStructure(variant.oldStructure()), m_graph.registerStructure(variant.newStructure())); | 
|  |  | 
|  | Edge propertyStorage; | 
|  |  | 
|  | if (isInlineOffset(variant.offset())) | 
|  | propertyStorage = node->child1(); | 
|  | else | 
|  | propertyStorage = Edge(m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, GetButterfly, origin, node->child1())); | 
|  |  | 
|  | StorageAccessData& data = *m_graph.m_storageAccessData.add(); | 
|  | data.offset = variant.offset(); | 
|  | data.identifierNumber = identifierNumber; | 
|  |  | 
|  | Node* clearValue = m_insertionSet.insertNode(indexInBlock, SpecNone, JSConstant, origin, OpInfo(m_graph.freezeStrong(JSValue()))); | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, PutByOffset, origin, OpInfo(&data), propertyStorage, node->child1(), Edge(clearValue)); | 
|  | origin = origin.withInvalidExit(); | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, PutStructure, origin, OpInfo(transition), | 
|  | node->child1()); | 
|  | m_graph.convertToConstant(node, jsBoolean(variant.result())); | 
|  | node->origin = origin; | 
|  | } | 
|  |  | 
|  | void addBaseCheck( | 
|  | unsigned indexInBlock, Node* node, const AbstractValue& baseValue, const StructureSet& set) | 
|  | { | 
|  | addBaseCheck(indexInBlock, node, baseValue, *m_graph.addStructureSet(set)); | 
|  | } | 
|  |  | 
|  | void addBaseCheck( | 
|  | unsigned indexInBlock, Node* node, const AbstractValue& baseValue, const RegisteredStructureSet& set) | 
|  | { | 
|  | if (!baseValue.m_structure.isSubsetOf(set)) { | 
|  | // Arises when we prune MultiGetByOffset. We could have a | 
|  | // MultiGetByOffset with a single variant that checks for structure S, | 
|  | // and the input has structures S and T, for example. | 
|  | ASSERT(node->child1()); | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, CheckStructure, node->origin, | 
|  | OpInfo(m_graph.addStructureSet(set.toStructureSet())), node->child1()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (baseValue.m_type & ~SpecCell) | 
|  | m_insertionSet.insertCheck(indexInBlock, node->origin, node->child1()); | 
|  | } | 
|  |  | 
|  | void addStructureTransitionCheck(NodeOrigin origin, unsigned indexInBlock, JSCell* cell, Structure* structure) | 
|  | { | 
|  | { | 
|  | StructureRegistrationResult result; | 
|  | m_graph.registerStructure(cell->structure(m_graph.m_vm), result); | 
|  | if (result == StructureRegisteredAndWatched) | 
|  | return; | 
|  | } | 
|  |  | 
|  | m_graph.registerStructure(structure); | 
|  |  | 
|  | Node* weakConstant = m_insertionSet.insertNode( | 
|  | indexInBlock, speculationFromValue(cell), JSConstant, origin, | 
|  | OpInfo(m_graph.freeze(cell))); | 
|  |  | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, CheckStructure, origin, | 
|  | OpInfo(m_graph.addStructureSet(structure)), Edge(weakConstant, CellUse)); | 
|  | } | 
|  |  | 
|  | void fixUpsilons(BasicBlock* block) | 
|  | { | 
|  | for (unsigned nodeIndex = block->size(); nodeIndex--;) { | 
|  | Node* node = block->at(nodeIndex); | 
|  | if (node->op() != Upsilon) | 
|  | continue; | 
|  | switch (node->phi()->op()) { | 
|  | case Phi: | 
|  | break; | 
|  | case JSConstant: | 
|  | case DoubleConstant: | 
|  | case Int52Constant: | 
|  | node->remove(m_graph); | 
|  | break; | 
|  | default: | 
|  | DFG_CRASH(m_graph, node, "Bad Upsilon phi() pointer"); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void tryFoldAsPutByOffset(Node* node, unsigned indexInBlock, Edge baseEdge, Edge valueEdge, bool isDirect, PrivateFieldPutKind privateFieldPutKind, bool& changed, bool& alreadyHandled) | 
|  | { | 
|  | if (!Options::useAccessInlining()) | 
|  | return; | 
|  |  | 
|  | NodeOrigin origin = node->origin; | 
|  | Node* baseNode = baseEdge.node(); | 
|  | UniquedStringImpl* uid = node->cacheableIdentifier().uid(); | 
|  |  | 
|  | ASSERT(baseEdge.useKind() == CellUse); | 
|  |  | 
|  | AbstractValue baseValue = m_state.forNode(baseNode); | 
|  | AbstractValue valueValue = m_state.forNode(valueEdge); | 
|  |  | 
|  | if (!baseValue.m_structure.isFinite()) | 
|  | return; | 
|  |  | 
|  | PutByIdStatus status = PutByIdStatus::computeFor( | 
|  | m_graph.globalObjectFor(origin.semantic), | 
|  | baseValue.m_structure.toStructureSet(), | 
|  | node->cacheableIdentifier().uid(), | 
|  | isDirect, privateFieldPutKind); | 
|  |  | 
|  | if (!status.isSimple()) | 
|  | return; | 
|  |  | 
|  | ASSERT(status.numVariants()); | 
|  |  | 
|  | if (status.numVariants() > 1 && !m_graph.m_plan.isFTL()) | 
|  | return; | 
|  |  | 
|  | changed = true; | 
|  |  | 
|  | RegisteredStructureSet newSet; | 
|  | TransitionVector transitions; | 
|  | for (const PutByIdVariant& variant : status.variants()) { | 
|  | if (variant.kind() == PutByIdVariant::Transition) { | 
|  | for (const ObjectPropertyCondition& condition : variant.conditionSet()) { | 
|  | if (m_graph.watchCondition(condition)) | 
|  | continue; | 
|  |  | 
|  | Structure* structure = condition.object()->structure(m_graph.m_vm); | 
|  | if (!condition.structureEnsuresValidity(structure)) | 
|  | return; | 
|  |  | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, CheckStructure, node->origin, | 
|  | OpInfo(m_graph.addStructureSet(structure)), | 
|  | m_insertionSet.insertConstantForUse( | 
|  | indexInBlock, node->origin, condition.object(), KnownCellUse)); | 
|  | } | 
|  |  | 
|  | ASSERT(privateFieldPutKind.isNone() || privateFieldPutKind.isDefine()); | 
|  | RegisteredStructure newStructure = m_graph.registerStructure(variant.newStructure()); | 
|  | transitions.append( | 
|  | Transition( | 
|  | m_graph.registerStructure(variant.oldStructureForTransition()), newStructure)); | 
|  | newSet.add(newStructure); | 
|  | } else { | 
|  | ASSERT(variant.kind() == PutByIdVariant::Replace); | 
|  | ASSERT(privateFieldPutKind.isNone() || privateFieldPutKind.isSet()); | 
|  | DFG_ASSERT(m_graph, node, variant.conditionSet().isEmpty()); | 
|  | newSet.merge(*m_graph.addStructureSet(variant.oldStructure())); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Push CFA over this node after we get the state before. | 
|  | m_interpreter.didFoldClobberWorld(); | 
|  | m_interpreter.observeTransitions(indexInBlock, transitions); | 
|  | if (m_state.forNode(baseEdge).changeStructure(m_graph, newSet) == Contradiction) | 
|  | m_state.setIsValid(false); | 
|  |  | 
|  | alreadyHandled = true; // Don't allow the default constant folder to do things to this. | 
|  |  | 
|  | m_insertionSet.insertNode( | 
|  | indexInBlock, SpecNone, FilterPutByIdStatus, node->origin, | 
|  | OpInfo(m_graph.m_plan.recordedStatuses().addPutByIdStatus(node->origin.semantic, status)), | 
|  | Edge(baseNode)); | 
|  |  | 
|  | unsigned identifierNumber = m_graph.identifiers().ensure(uid); | 
|  | if (status.numVariants() == 1) { | 
|  | emitPutByOffset(indexInBlock, node, baseValue, status[0], identifierNumber); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ASSERT(m_graph.m_plan.isFTL()); | 
|  |  | 
|  | MultiPutByOffsetData* data = m_graph.m_multiPutByOffsetData.add(); | 
|  | data->variants = status.variants(); | 
|  | data->identifierNumber = identifierNumber; | 
|  | node->convertToMultiPutByOffset(data); | 
|  | } | 
|  |  | 
|  | InPlaceAbstractState m_state; | 
|  | AbstractInterpreter<InPlaceAbstractState> m_interpreter; | 
|  | InsertionSet m_insertionSet; | 
|  | }; | 
|  |  | 
|  | bool performConstantFolding(Graph& graph) | 
|  | { | 
|  | return runPhase<ConstantFoldingPhase>(graph); | 
|  | } | 
|  |  | 
|  | } } // namespace JSC::DFG | 
|  |  | 
|  | #endif // ENABLE(DFG_JIT) | 
|  |  | 
|  |  |