diff options
Diffstat (limited to 'Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp')
-rw-r--r-- | Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp | 898 |
1 files changed, 631 insertions, 267 deletions
diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp index 39ac2ff7a..e5be8efb5 100644 --- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2012-2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -28,13 +28,16 @@ #if ENABLE(DFG_JIT) -#include "DFGAbstractState.h" -#include "DFGBasicBlock.h" +#include "DFGAbstractInterpreterInlines.h" +#include "DFGArgumentsUtilities.h" +#include "DFGBasicBlockInlines.h" #include "DFGGraph.h" +#include "DFGInPlaceAbstractState.h" +#include "DFGInferredTypeCheck.h" #include "DFGInsertionSet.h" #include "DFGPhase.h" #include "GetByIdStatus.h" -#include "Operations.h" +#include "JSCInlines.h" #include "PutByIdStatus.h" namespace JSC { namespace DFG { @@ -44,6 +47,7 @@ public: ConstantFoldingPhase(Graph& graph) : Phase(graph, "constant folding") , m_state(graph) + , m_interpreter(graph, m_state) , m_insertionSet(graph) { } @@ -51,27 +55,59 @@ public: bool run() { bool changed = false; - - for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { - BasicBlock* block = m_graph.m_blocks[blockIndex].get(); - if (!block) - continue; - if (!block->cfaDidFinish) - changed |= paintUnreachableCode(blockIndex); + + for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { if (block->cfaFoundConstants) - changed |= foldConstants(blockIndex); + 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; + 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) + m_graph.m_allocator.free(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.invalidateCFG(); + m_graph.resetReachability(); + m_graph.killUnreachableBlocks(); + } + } + return changed; } private: - bool foldConstants(BlockIndex blockIndex) + bool foldConstants(BasicBlock* block) { -#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) - dataLogF("Constant folding considering Block #%u.\n", blockIndex); -#endif - BasicBlock* block = m_graph.m_blocks[blockIndex].get(); bool changed = false; m_state.beginBasicBlock(block); for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { @@ -80,21 +116,26 @@ private: Node* node = block->at(indexInBlock); + bool alreadyHandled = false; bool eliminated = false; switch (node->op()) { - case CheckArgumentsNotCreated: { - if (!isEmptySpeculation( - m_state.variables().operand( - m_graph.argumentsRegisterFor(node->codeOrigin)).m_type)) - break; - node->convertToPhantom(); - eliminated = true; + case BooleanToNumber: { + if (node->child1().useKind() == UntypedUse + && !m_interpreter.needsTypeCheck(node->child1(), SpecBoolean)) + node->child1().setUseKind(BooleanUse); break; } - + + case CompareEq: { + if (!m_interpreter.needsTypeCheck(node->child1(), SpecOther)) + node->child1().setUseKind(OtherUse); + if (!m_interpreter.needsTypeCheck(node->child2(), SpecOther)) + node->child2().setUseKind(OtherUse); + break; + } + case CheckStructure: - case ForwardCheckStructure: case ArrayifyToStructure: { AbstractValue& value = m_state.forNode(node->child1()); StructureSet set; @@ -102,20 +143,70 @@ private: set = node->structure(); else set = node->structureSet(); - if (value.m_currentKnownStructure.isSubsetOf(set)) { - m_state.execute(indexInBlock); // Catch the fact that we may filter on cell. - node->convertToPhantom(); + if (value.m_structure.isSubsetOf(set)) { + m_interpreter.execute(indexInBlock); // Catch the fact that we may filter on cell. + node->remove(); eliminated = true; break; } - StructureAbstractValue& structureValue = value.m_futurePossibleStructure; - if (structureValue.isSubsetOf(set) - && structureValue.hasSingleton()) { - Structure* structure = structureValue.singleton(); - m_state.execute(indexInBlock); // Catch the fact that we may filter on cell. - node->convertToStructureTransitionWatchpoint(structure); - eliminated = true; + 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()); + StructureSet& set = node->structureSet(); + + if (value.value()) { + if (Structure* structure = jsDynamicCast<Structure*>(value.value())) { + if (set.contains(structure)) { + m_interpreter.execute(indexInBlock); + node->remove(); + eliminated = true; + break; + } + } + } + + if (PhiChildren* phiChildren = m_interpreter.phiChildren()) { + bool allGood = true; + phiChildren->forAllTransitiveIncomingValues( + node, + [&] (Node* incoming) { + if (Structure* structure = incoming->dynamicCastConstant<Structure*>()) { + if (set.contains(structure)) + return; + } + allGood = false; + }); + if (allGood) { + m_interpreter.execute(indexInBlock); + node->remove(); + eliminated = true; + break; + } } break; } @@ -124,249 +215,406 @@ private: case Arrayify: { if (!node->arrayMode().alreadyChecked(m_graph, node, m_state.forNode(node->child1()))) break; - node->convertToPhantom(); + node->remove(); eliminated = true; break; } - case CheckFunction: { - if (m_state.forNode(node->child1()).value() != node->function()) + case PutStructure: { + if (m_state.forNode(node->child1()).m_structure.onlyStructure() != node->transition()->next) break; - node->convertToPhantom(); + + node->remove(); eliminated = true; break; } - case GetById: - case GetByIdFlush: { - CodeOrigin codeOrigin = node->codeOrigin; - Edge childEdge = node->child1(); - Node* child = childEdge.node(); - unsigned identifierNumber = node->identifierNumber(); - - if (childEdge.useKind() != CellUse) + case CheckCell: { + if (m_state.forNode(node->child1()).value() != node->cellOperand()->value()) + break; + node->remove(); + eliminated = true; + break; + } + + case CheckNotEmpty: { + if (m_state.forNode(node->child1()).m_type & SpecEmpty) + break; + node->remove(); + eliminated = true; + break; + } + + case CheckIdent: { + UniquedStringImpl* uid = node->uidOperand(); + const UniquedStringImpl* constantUid = nullptr; + + JSValue childConstant = m_state.forNode(node->child1()).value(); + if (childConstant) { + if (uid->isSymbol()) { + if (childConstant.isSymbol()) + constantUid = asSymbol(childConstant)->privateName().uid(); + } else { + 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->isAtomic()) + constantUid = static_cast<const UniquedStringImpl*>(impl); + } + } + } + } + + if (constantUid == uid) { + node->remove(); + 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->remove(); + eliminated = true; break; + } - Structure* structure = m_state.forNode(child).bestProvenStructure(); - if (!structure) + break; + } + + case GetMyArgumentByVal: { + JSValue index = m_state.forNode(node->child2()).value(); + if (!index || !index.isInt32()) break; - bool needsWatchpoint = !m_state.forNode(child).m_currentKnownStructure.hasSingleton(); - bool needsCellCheck = m_state.forNode(child).m_type & ~SpecCell; + 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.asUInt32() >= inlineCallFrame->arguments.size() - 1) + break; + } else { + if (index.asUInt32() >= m_state.variables().numberOfArguments() - 1) + break; + } + + m_interpreter.execute(indexInBlock); // Push CFA over this node after we get the state before. - GetByIdStatus status = GetByIdStatus::computeFor( - vm(), structure, codeBlock()->identifier(identifierNumber)); + StackAccessData* data; + if (inlineCallFrame) { + data = m_graph.m_stackAccessData.add( + VirtualRegister( + inlineCallFrame->stackOffset + + CallFrame::argumentOffset(index.asInt32())), + FlushedJSValue); + } else { + data = m_graph.m_stackAccessData.add( + virtualRegisterForArgument(index.asInt32() + 1), FlushedJSValue); + } - if (!status.isSimple()) { - // FIXME: We could handle prototype cases. - // https://bugs.webkit.org/show_bug.cgi?id=110386 + if (inlineCallFrame && !inlineCallFrame->isVarargs() + && index.asUInt32() < inlineCallFrame->arguments.size() - 1) { + node->convertToGetStack(data); + eliminated = true; break; } - ASSERT(status.structureSet().size() == 1); - ASSERT(status.chain().isEmpty()); - ASSERT(status.structureSet().singletonStructure() == structure); - - // Now before we do anything else, push the CFA forward over the GetById - // and make sure we signal to the loop that it should continue and not - // do any eliminations. - m_state.execute(indexInBlock); + Node* length = emitCodeToGetArgumentsArrayLength( + m_insertionSet, arguments, indexInBlock, node->origin); + m_insertionSet.insertNode( + indexInBlock, SpecNone, CheckInBounds, node->origin, + node->child2(), Edge(length, Int32Use)); + node->convertToGetStack(data); 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; + } - if (needsWatchpoint) { - ASSERT(m_state.forNode(child).m_futurePossibleStructure.isSubsetOf(StructureSet(structure))); - m_insertionSet.insertNode( - indexInBlock, SpecNone, StructureTransitionWatchpoint, codeOrigin, - OpInfo(structure), childEdge); - } else if (needsCellCheck) { - m_insertionSet.insertNode( - indexInBlock, SpecNone, Phantom, codeOrigin, childEdge); + 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().filter(baseValue); + + 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(), + variant.requiredType()); + changed = true; + } } + + if (data.variants.size() != 1) + break; + + emitPutByOffset( + indexInBlock, node, baseValue, data.variants[0], data.identifierNumber); + changed = true; + break; + } + + case GetById: + case GetByIdFlush: { + Edge childEdge = node->child1(); + Node* child = childEdge.node(); + unsigned identifierNumber = node->identifierNumber(); - childEdge.setUseKind(KnownCellUse); + 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 (baseValue.m_structure.isTop() || baseValue.m_structure.isClobbered() + || (node->child1().useKind() == UntypedUse || (baseValue.m_type & ~SpecCell))) + break; - Edge propertyStorage; + GetByIdStatus status = GetByIdStatus::computeFor( + baseValue.m_structure.set(), m_graph.identifiers()[identifierNumber]); + if (!status.isSimple()) + break; - if (isInlineOffset(status.offset())) - propertyStorage = childEdge; - else { - propertyStorage = Edge(m_insertionSet.insertNode( - indexInBlock, SpecNone, GetButterfly, codeOrigin, childEdge)); + 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; + } } - node->convertToGetByOffset(m_graph.m_storageAccessData.size(), propertyStorage); + if (status.numVariants() == 1) { + emitGetByOffset(indexInBlock, node, baseValue, status[0], identifierNumber); + changed = true; + break; + } - StorageAccessData storageAccessData; - storageAccessData.offset = indexRelativeToBase(status.offset()); - storageAccessData.identifierNumber = identifierNumber; - m_graph.m_storageAccessData.append(storageAccessData); + if (!isFTL(m_graph.m_plan.mode)) + break; + + MultiGetByOffsetData* data = m_graph.m_multiGetByOffsetData.add(); + for (const GetByIdVariant& variant : status.variants()) { + data->cases.append( + MultiGetByOffsetCase( + variant.structureSet(), + GetByOffsetMethod::load(variant.offset()))); + } + data->identifierNumber = identifierNumber; + node->convertToMultiGetByOffset(data); + changed = true; break; } case PutById: - case PutByIdDirect: { - CodeOrigin codeOrigin = node->codeOrigin; + case PutByIdDirect: + case PutByIdFlush: { + NodeOrigin origin = node->origin; Edge childEdge = node->child1(); Node* child = childEdge.node(); unsigned identifierNumber = node->identifierNumber(); ASSERT(childEdge.useKind() == CellUse); - Structure* structure = m_state.forNode(child).bestProvenStructure(); - if (!structure) + AbstractValue baseValue = m_state.forNode(child); + AbstractValue valueValue = m_state.forNode(node->child2()); + + 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 (baseValue.m_structure.isTop() || baseValue.m_structure.isClobbered()) break; - bool needsWatchpoint = !m_state.forNode(child).m_currentKnownStructure.hasSingleton(); - bool needsCellCheck = m_state.forNode(child).m_type & ~SpecCell; - PutByIdStatus status = PutByIdStatus::computeFor( - vm(), - m_graph.globalObjectFor(codeOrigin), - structure, - codeBlock()->identifier(identifierNumber), + m_graph.globalObjectFor(origin.semantic), + baseValue.m_structure.set(), + m_graph.identifiers()[identifierNumber], node->op() == PutByIdDirect); - if (!status.isSimpleReplace() && !status.isSimpleTransition()) + if (!status.isSimple()) break; + + ASSERT(status.numVariants()); - ASSERT(status.oldStructure() == structure); - - // Now before we do anything else, push the CFA forward over the PutById - // and make sure we signal to the loop that it should continue and not - // do any eliminations. - m_state.execute(indexInBlock); - eliminated = true; - - if (needsWatchpoint) { - ASSERT(m_state.forNode(child).m_futurePossibleStructure.isSubsetOf(StructureSet(structure))); - m_insertionSet.insertNode( - indexInBlock, SpecNone, StructureTransitionWatchpoint, codeOrigin, - OpInfo(structure), childEdge); - } else if (needsCellCheck) { - m_insertionSet.insertNode( - indexInBlock, SpecNone, Phantom, codeOrigin, childEdge); - } - - childEdge.setUseKind(KnownCellUse); + if (status.numVariants() > 1 && !isFTL(m_graph.m_plan.mode)) + break; - StructureTransitionData* transitionData = 0; - if (status.isSimpleTransition()) { - transitionData = m_graph.addStructureTransitionData( - StructureTransitionData(structure, status.newStructure())); - - if (node->op() == PutById) { - if (!structure->storedPrototype().isNull()) { - addStructureTransitionCheck( - codeOrigin, indexInBlock, - structure->storedPrototype().asCell()); - } - - for (WriteBarrier<Structure>* it = status.structureChain()->head(); *it; ++it) { - JSValue prototype = (*it)->storedPrototype(); - if (prototype.isNull()) - continue; - ASSERT(prototype.isCell()); - addStructureTransitionCheck( - codeOrigin, indexInBlock, prototype.asCell()); + changed = true; + + bool allGood = true; + for (const PutByIdVariant& variant : status.variants()) { + if (!allGood) + break; + for (const ObjectPropertyCondition& condition : variant.conditionSet()) { + if (m_graph.watchCondition(condition)) + continue; + + Structure* structure = condition.object()->structure(); + if (!condition.structureEnsuresValidity(structure)) { + allGood = false; + break; } + + m_insertionSet.insertNode( + indexInBlock, SpecNone, CheckStructure, node->origin, + OpInfo(m_graph.addStructureSet(structure)), + m_insertionSet.insertConstantForUse( + indexInBlock, node->origin, condition.object(), KnownCellUse)); } } + + if (!allGood) + break; - Edge propertyStorage; - - if (isInlineOffset(status.offset())) - propertyStorage = childEdge; - else if (status.isSimpleReplace() || structure->outOfLineCapacity() == status.newStructure()->outOfLineCapacity()) { - propertyStorage = Edge(m_insertionSet.insertNode( - indexInBlock, SpecNone, GetButterfly, codeOrigin, childEdge)); - } else if (!structure->outOfLineCapacity()) { - ASSERT(status.newStructure()->outOfLineCapacity()); - ASSERT(!isInlineOffset(status.offset())); - propertyStorage = Edge(m_insertionSet.insertNode( - indexInBlock, SpecNone, AllocatePropertyStorage, - codeOrigin, OpInfo(transitionData), childEdge)); - } else { - ASSERT(structure->outOfLineCapacity()); - ASSERT(status.newStructure()->outOfLineCapacity() > structure->outOfLineCapacity()); - ASSERT(!isInlineOffset(status.offset())); - - propertyStorage = Edge(m_insertionSet.insertNode( - indexInBlock, SpecNone, ReallocatePropertyStorage, codeOrigin, - OpInfo(transitionData), childEdge, - Edge(m_insertionSet.insertNode( - indexInBlock, SpecNone, GetButterfly, codeOrigin, childEdge)))); - } - - if (status.isSimpleTransition()) { - m_insertionSet.insertNode( - indexInBlock, SpecNone, PutStructure, codeOrigin, - OpInfo(transitionData), childEdge); + if (status.numVariants() == 1) { + emitPutByOffset(indexInBlock, node, baseValue, status[0], identifierNumber); + break; } - node->convertToPutByOffset(m_graph.m_storageAccessData.size(), propertyStorage); + ASSERT(isFTL(m_graph.m_plan.mode)); + + MultiPutByOffsetData* data = m_graph.m_multiPutByOffsetData.add(); + data->variants = status.variants(); + data->identifierNumber = identifierNumber; + node->convertToMultiPutByOffset(data); + break; + } + + case ToPrimitive: { + if (m_state.forNode(node->child1()).m_type & ~(SpecFullNumber | SpecBoolean | SpecString | SpecSymbol)) + break; - StorageAccessData storageAccessData; - storageAccessData.offset = indexRelativeToBase(status.offset()); - storageAccessData.identifierNumber = identifierNumber; - m_graph.m_storageAccessData.append(storageAccessData); + node->convertToIdentity(); + changed = true; + 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; } default: break; } - + if (eliminated) { changed = true; continue; } - m_state.execute(indexInBlock); + 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()) continue; - JSValue value = m_state.forNode(node).value(); - if (!value) + + // 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; - - CodeOrigin codeOrigin = node->codeOrigin; - AdjacencyList children = node->children; if (node->op() == GetLocal) { - // GetLocals without a Phi child are guaranteed dead. We don't have to - // do anything about them. - if (!node->child1()) - continue; - - if (m_graph.m_form != LoadStore) { - VariableAccessData* variable = node->variableAccessData(); - Node* phi = node->child1().node(); - if (phi->op() == Phi - && block->variablesAtHead.operand(variable->local()) == phi - && block->variablesAtTail.operand(variable->local()) == node) { - - // Keep the graph threaded for easy cases. This is improves compile - // times. It would be correct to just dethread here. - - m_graph.convertToConstant(node, value); - Node* phantom = m_insertionSet.insertNode( - indexInBlock, SpecNone, PhantomLocal, codeOrigin, - OpInfo(variable), Edge(phi)); - block->variablesAtHead.operand(variable->local()) = phantom; - block->variablesAtTail.operand(variable->local()) = phantom; - - changed = true; - - continue; - } - - m_graph.dethread(); - } + // 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 - ASSERT(!node->hasVariableAccessData()); - + m_insertionSet.insertCheck(indexInBlock, node->origin, node->children); m_graph.convertToConstant(node, value); - m_insertionSet.insertNode( - indexInBlock, SpecNone, Phantom, codeOrigin, children); changed = true; } @@ -376,85 +624,201 @@ private: return changed; } -#if !ASSERT_DISABLED - bool isCapturedAtOrAfter(BasicBlock* block, unsigned indexInBlock, int operand) + void emitGetByOffset(unsigned indexInBlock, Node* node, const AbstractValue& baseValue, const MultiGetByOffsetCase& getCase, unsigned identifierNumber) { - for (; indexInBlock < block->size(); ++indexInBlock) { - Node* node = block->at(indexInBlock); - if (!node->hasLocal()) - continue; - if (node->local() != operand) - continue; - if (node->variableAccessData()->isCaptured()) - return true; + // 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, variant.structureSet(), variant.offset())) { + m_graph.convertToConstant(node, m_graph.freeze(value)); + return; } - return false; + + emitGetByOffset(indexInBlock, node, childEdge, identifierNumber, variant.offset()); } -#endif // !ASSERT_DISABLED - void addStructureTransitionCheck(CodeOrigin codeOrigin, unsigned indexInBlock, JSCell* cell) + void emitGetByOffset( + unsigned indexInBlock, Node* node, Edge childEdge, unsigned identifierNumber, + PropertyOffset offset, const InferredType::Descriptor& inferredType = InferredType::Top) { - Node* weakConstant = m_insertionSet.insertNode( - indexInBlock, speculationFromValue(cell), WeakJSConstant, codeOrigin, OpInfo(cell)); + childEdge.setUseKind(KnownCellUse); + + Edge propertyStorage; + + if (isInlineOffset(offset)) + propertyStorage = childEdge; + else { + propertyStorage = Edge(m_insertionSet.insertNode( + indexInBlock, SpecNone, GetButterfly, node->origin, childEdge)); + } - if (cell->structure()->transitionWatchpointSetIsStillValid()) { + StorageAccessData& data = *m_graph.m_storageAccessData.add(); + data.offset = offset; + data.identifierNumber = identifierNumber; + data.inferredType = inferredType; + + node->convertToGetByOffset(data, propertyStorage); + } + + 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()); + insertInferredTypeCheck( + m_insertionSet, indexInBlock, origin, node->child2().node(), variant.requiredType()); + + node->child1().setUseKind(KnownCellUse); + childEdge.setUseKind(KnownCellUse); + + Transition* transition = 0; + if (variant.kind() == PutByIdVariant::Transition) { + transition = m_graph.m_transitions.add( + variant.oldStructureForTransition(), variant.newStructure()); + } + + Edge propertyStorage; + + DFG_ASSERT(m_graph, node, origin.exitOK); + bool canExit = true; + + 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.takeValidExit(canExit), OpInfo(transition), childEdge); + propertyStorage = Edge(allocatePropertyStorage); + } 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.takeValidExit(canExit), + OpInfo(transition), childEdge, + Edge(m_insertionSet.insertNode( + indexInBlock, SpecNone, GetButterfly, origin, childEdge))); + propertyStorage = Edge(reallocatePropertyStorage); + } + + StorageAccessData& data = *m_graph.m_storageAccessData.add(); + data.offset = variant.offset(); + data.identifierNumber = identifierNumber; + + node->convertToPutByOffset(data, propertyStorage); + node->origin.exitOK = canExit; + + if (variant.kind() == PutByIdVariant::Transition) { + // 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, SpecNone, StructureTransitionWatchpoint, codeOrigin, - OpInfo(cell->structure()), Edge(weakConstant, CellUse)); + indexInBlock + 1, SpecNone, PutStructure, origin.withInvalidExit(), OpInfo(transition), + childEdge); + } + } + + void addBaseCheck( + unsigned indexInBlock, Node* node, const AbstractValue& baseValue, const StructureSet& 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)), node->child1()); return; } - - m_insertionSet.insertNode( - indexInBlock, SpecNone, CheckStructure, codeOrigin, - OpInfo(m_graph.addStructureSet(cell->structure())), Edge(weakConstant, CellUse)); + + if (baseValue.m_type & ~SpecCell) + m_insertionSet.insertCheck(indexInBlock, node->origin, node->child1()); } - // This is necessary because the CFA may reach conclusions about constants based on its - // assumption that certain code must exit, but then those constants may lead future - // reexecutions of the CFA to believe that the same code will now no longer exit. Thus - // to ensure soundness, we must paint unreachable code as such, by inserting an - // unconditional ForceOSRExit wherever we find that a node would have always exited. - // This will only happen in cases where we are making static speculations, or we're - // making totally wrong speculations due to imprecision on the prediction propagator. - bool paintUnreachableCode(BlockIndex blockIndex) + void addStructureTransitionCheck(NodeOrigin origin, unsigned indexInBlock, JSCell* cell, Structure* structure) { - bool changed = false; + if (m_graph.registerStructure(cell->structure()) == StructureRegisteredAndWatched) + return; -#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) - dataLogF("Painting unreachable code in Block #%u.\n", blockIndex); -#endif - BasicBlock* block = m_graph.m_blocks[blockIndex].get(); - m_state.beginBasicBlock(block); + m_graph.registerStructure(structure); + + Node* weakConstant = m_insertionSet.insertNode( + indexInBlock, speculationFromValue(cell), JSConstant, origin, + OpInfo(m_graph.freeze(cell))); - for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { - m_state.execute(indexInBlock); - if (m_state.isValid()) + 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; - - Node* node = block->at(indexInBlock); - switch (node->op()) { - case Return: - case Throw: - case ThrowReferenceError: - case ForceOSRExit: - // Do nothing. These nodes will already do the right thing. + switch (node->phi()->op()) { + case Phi: + break; + case JSConstant: + case DoubleConstant: + case Int52Constant: + node->remove(); break; - default: - m_insertionSet.insertNode( - indexInBlock, SpecNone, ForceOSRExit, node->codeOrigin); - changed = true; + DFG_CRASH(m_graph, node, "Bad Upsilon phi() pointer"); break; } - break; } - m_state.reset(); - m_insertionSet.execute(block); - - return changed; } - - AbstractState m_state; + + InPlaceAbstractState m_state; + AbstractInterpreter<InPlaceAbstractState> m_interpreter; InsertionSet m_insertionSet; }; |