bool mayExit(Graph& graph, Node* node) { switch (node->op()) { case SetArgument: case JSConstant: case DoubleConstant: case Int52Constant: case MovHint: case SetLocal: case Flush: case Phantom: case Check: case HardPhantom: case GetLocal: case LoopHint: case PhantomArguments: case Phi: case Upsilon: case ZombieHint: break; default: // If in doubt, return true. return true; } EdgeMayExit functor; DFG_NODE_DO_TO_CHILDREN(graph, node, functor); return functor.result(); }
void process(BlockIndex blockIndex) { BasicBlock* block = m_graph.block(blockIndex); if (!block) return; // FIXME: It's likely that this can be improved, for static analyses that use // HashSets. https://bugs.webkit.org/show_bug.cgi?id=118455 m_live = block->ssa->liveAtTail; for (unsigned nodeIndex = block->size(); nodeIndex--;) { Node* node = block->at(nodeIndex); // Given an Upsilon: // // n: Upsilon(@x, ^p) // // We say that it def's @p and @n and uses @x. // // Given a Phi: // // p: Phi() // // We say nothing. It's neither a use nor a def. // // Given a node: // // n: Thingy(@a, @b, @c) // // We say that it def's @n and uses @a, @b, @c. switch (node->op()) { case Upsilon: { Node* phi = node->phi(); m_live.remove(phi); m_live.remove(node); m_live.add(node->child1().node()); break; } case Phi: { break; } default: m_live.remove(node); DFG_NODE_DO_TO_CHILDREN(m_graph, node, addChildUse); break; } } if (m_live == block->ssa->liveAtHead) return; m_changed = true; block->ssa->liveAtHead = m_live; for (unsigned i = block->predecessors.size(); i--;) block->predecessors[i]->ssa->liveAtTail.add(m_live.begin(), m_live.end()); }
bool run() { ASSERT(m_graph.m_form == ThreadedCPS || m_graph.m_form == SSA); // First reset the counts to 0 for all nodes. for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; for (unsigned indexInBlock = block->size(); indexInBlock--;) block->at(indexInBlock)->setRefCount(0); for (unsigned phiIndex = block->phis.size(); phiIndex--;) block->phis[phiIndex]->setRefCount(0); } // Now find the roots: // - Nodes that are must-generate. // - Nodes that are reachable from type checks. // Set their ref counts to 1 and put them on the worklist. for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; for (unsigned indexInBlock = block->size(); indexInBlock--;) { Node* node = block->at(indexInBlock); DFG_NODE_DO_TO_CHILDREN(m_graph, node, findTypeCheckRoot); if (!(node->flags() & NodeMustGenerate)) continue; if (!node->postfixRef()) m_worklist.append(node); } } while (!m_worklist.isEmpty()) { while (!m_worklist.isEmpty()) { Node* node = m_worklist.last(); m_worklist.removeLast(); ASSERT(node->shouldGenerate()); // It should not be on the worklist unless it's ref'ed. DFG_NODE_DO_TO_CHILDREN(m_graph, node, countEdge); } if (m_graph.m_form == SSA) { // Find Phi->Upsilon edges, which are represented as meta-data in the // Upsilon. for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; for (unsigned nodeIndex = block->size(); nodeIndex--;) { Node* node = block->at(nodeIndex); if (node->op() != Upsilon) continue; if (node->shouldGenerate()) continue; if (node->phi()->shouldGenerate()) countNode(node); } } } } if (m_graph.m_form == SSA) { // Need to process the graph in reverse DFS order, so that we get to the uses // of a node before we get to the node itself. Vector<BasicBlock*> depthFirst; m_graph.getBlocksInDepthFirstOrder(depthFirst); for (unsigned i = depthFirst.size(); i--;) fixupBlock(depthFirst[i]); } else { RELEASE_ASSERT(m_graph.m_form == ThreadedCPS); for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex) fixupBlock(m_graph.block(blockIndex)); cleanVariables(m_graph.m_arguments); } m_graph.m_refCountState = ExactRefCount; return true; }
ExitMode mayExit(Graph& graph, Node* node) { ExitMode result = DoesNotExit; switch (node->op()) { // This is a carefully curated list of nodes that definitely do not exit. We try to be very // conservative when maintaining this list, because adding new node types to it doesn't // generally make things a lot better but it might introduce subtle bugs. case SetArgument: case JSConstant: case DoubleConstant: case Int52Constant: case MovHint: case SetLocal: case Flush: case Phantom: case Check: case Identity: case GetLocal: case LoopHint: case Phi: case Upsilon: case ZombieHint: case ExitOK: case BottomValue: case PutHint: case PhantomNewObject: case PutStack: case KillStack: case GetStack: case GetCallee: case GetArgumentCount: case GetRestLength: case GetScope: case PhantomLocal: case CountExecution: case Jump: case Branch: case Unreachable: case DoubleRep: case Int52Rep: case ValueRep: case ExtractOSREntryLocal: case LogicalNot: case NotifyWrite: case PutStructure: case StoreBarrier: case PutByOffset: case PutClosureVar: break; case StrCat: case Call: case Construct: case CallVarargs: case ConstructVarargs: case CallForwardVarargs: case ConstructForwardVarargs: case MaterializeCreateActivation: case MaterializeNewObject: case NewFunction: case NewArrowFunction: case NewStringObject: case CreateActivation: result = ExitsForExceptions; break; default: // If in doubt, return true. return Exits; } EdgeMayExit functor; DFG_NODE_DO_TO_CHILDREN(graph, node, functor); if (functor.result()) result = Exits; return result; }
bool run() { RELEASE_ASSERT(m_graph.m_form == ThreadedCPS); if (dumpGraph) { dataLog("Graph dump at top of SSA conversion:\n"); m_graph.dump(); } // Figure out which SetLocal's need flushing. Need to do this while the // Phi graph is still intact. for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; for (unsigned nodeIndex = block->size(); nodeIndex--;) { Node* node = block->at(nodeIndex); if (node->op() != Flush) continue; addFlushedLocalOp(node); } } while (!m_flushedLocalOpWorklist.isEmpty()) { Node* node = m_flushedLocalOpWorklist.takeLast(); ASSERT(m_flushedLocalOps.contains(node)); DFG_NODE_DO_TO_CHILDREN(m_graph, node, addFlushedLocalEdge); } // Eliminate all duplicate or self-pointing Phi edges. This means that // we transform: // // p: Phi(@n1, @n2, @n3) // // into: // // p: Phi(@x) // // if each @ni in {@n1, @n2, @n3} is either equal to @p to is equal // to @x, for exactly one other @x. Additionally, trivial Phis (i.e. // p: Phi(@x)) are forwarded, so that if have an edge to such @p, we // replace it with @x. This loop does this for Phis only; later we do // such forwarding for Phi references found in other nodes. // // See Aycock and Horspool in CC'00 for a better description of what // we're doing here. do { m_changed = false; for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; for (unsigned phiIndex = block->phis.size(); phiIndex--;) { Node* phi = block->phis[phiIndex]; if (phi->variableAccessData()->isCaptured()) continue; forwardPhiChildren(phi); deduplicateChildren(phi); } } } while (m_changed); // For each basic block, for each local live at the head of that block, // figure out what node we should be referring to instead of that local. // If it turns out to be a non-trivial Phi, make sure that we create an // SSA Phi and Upsilons in predecessor blocks. We reuse // BasicBlock::variablesAtHead for tracking which nodes to refer to. Operands<bool> nonTrivialPhis(OperandsLike, m_graph.block(0)->variablesAtHead); for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; nonTrivialPhis.fill(false); for (unsigned i = block->phis.size(); i--;) { Node* phi = block->phis[i]; if (!phi->children.justOneChild()) nonTrivialPhis.operand(phi->local()) = true; } for (unsigned i = block->variablesAtHead.size(); i--;) { Node* node = block->variablesAtHead[i]; if (!node) continue; if (verbose) dataLog("At block #", blockIndex, " for operand r", block->variablesAtHead.operandForIndex(i), " have node ", node, "\n"); VariableAccessData* variable = node->variableAccessData(); if (variable->isCaptured()) { // Poison this entry in variablesAtHead because we don't // want anyone to try to refer to it, if the variable is // captured. block->variablesAtHead[i] = 0; continue; } switch (node->op()) { case Phi: case SetArgument: break; case Flush: case GetLocal: case PhantomLocal: node = node->child1().node(); break; default: RELEASE_ASSERT_NOT_REACHED(); } RELEASE_ASSERT(node->op() == Phi || node->op() == SetArgument); bool isFlushed = m_flushedLocalOps.contains(node); if (node->op() == Phi) { if (!nonTrivialPhis.operand(node->local())) { Edge edge = node->children.justOneChild(); ASSERT(edge); if (verbose) dataLog(" One child: ", edge, ", ", RawPointer(edge.node()), "\n"); node = edge.node(); // It's something from a different basic block. } else { if (verbose) dataLog(" Non-trivial.\n"); // It's a non-trivial Phi. FlushFormat format = variable->flushFormat(); NodeFlags result = resultFor(format); UseKind useKind = useKindFor(format); node = m_insertionSet.insertNode(0, SpecNone, Phi, NodeOrigin()); if (verbose) dataLog(" Inserted new node: ", node, "\n"); node->mergeFlags(result); RELEASE_ASSERT((node->flags() & NodeResultMask) == result); for (unsigned j = block->predecessors.size(); j--;) { BasicBlock* predecessor = block->predecessors[j]; predecessor->appendNonTerminal( m_graph, SpecNone, Upsilon, predecessor->last()->origin, OpInfo(node), Edge(predecessor->variablesAtTail[i], useKind)); } if (isFlushed) { // Do nothing. For multiple reasons. // Reason #1: If the local is flushed then we don't need to bother // with a MovHint since every path to this point in the code will // have flushed the bytecode variable using a SetLocal and hence // the Availability::flushedAt() will agree, and that will be // sufficient for figuring out how to recover the variable's value. // Reason #2: If we had inserted a MovHint and the Phi function had // died (because the only user of the value was the "flush" - i.e. // some asynchronous runtime thingy) then the MovHint would turn // into a ZombieHint, which would fool us into thinking that the // variable is dead. // Reason #3: If we had inserted a MovHint then even if the Phi // stayed alive, we would still end up generating inefficient code // since we would be telling the OSR exit compiler to use some SSA // value for the bytecode variable rather than just telling it that // the value was already on the stack. } else { m_insertionSet.insertNode( 0, SpecNone, MovHint, NodeOrigin(), OpInfo(variable->local().offset()), Edge(node)); } } } block->variablesAtHead[i] = node; } m_insertionSet.execute(block); } if (verbose) { dataLog("Variables at head after SSA Phi insertion:\n"); for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; dataLog(" ", *block, ": ", block->variablesAtHead, "\n"); } } // At this point variablesAtHead in each block refers to either: // // 1) A new SSA phi in the current block. // 2) A SetArgument, which will soon get converted into a GetArgument. // 3) An old CPS phi in a different block. // // We don't have to do anything for (1) and (2), but we do need to // do a replacement for (3). // Clear all replacements, since other phases may have used them. m_graph.clearReplacements(); if (dumpGraph) { dataLog("Graph just before identifying replacements:\n"); m_graph.dump(); } // For all of the old CPS Phis, figure out what they correspond to in SSA. for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; if (verbose) dataLog("Dealing with block #", blockIndex, "\n"); for (unsigned phiIndex = block->phis.size(); phiIndex--;) { Node* phi = block->phis[phiIndex]; if (verbose) { dataLog( "Considering ", phi, " (", RawPointer(phi), "), for r", phi->local(), ", and its replacement in ", *block, ", ", block->variablesAtHead.operand(phi->local()), "\n"); } ASSERT(phi != block->variablesAtHead.operand(phi->local())); phi->misc.replacement = block->variablesAtHead.operand(phi->local()); } } // Now make sure that all variablesAtHead in each block points to the // canonical SSA value. Prior to this, variablesAtHead[local] may point to // an old CPS Phi in a different block. for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; for (size_t i = block->variablesAtHead.size(); i--;) { Node* node = block->variablesAtHead[i]; if (!node) continue; while (node->misc.replacement) { ASSERT(node != node->misc.replacement); node = node->misc.replacement; } block->variablesAtHead[i] = node; } } if (verbose) { dataLog("Variables at head after convergence:\n"); for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; dataLog(" ", *block, ": ", block->variablesAtHead, "\n"); } } // Convert operations over locals into operations over SSA nodes. // - GetLocal over captured variables lose their phis. // - GetLocal over uncaptured variables die and get replaced with references // to the node specified by variablesAtHead. // - SetLocal gets NodeMustGenerate if it's flushed, or turns into a // Check otherwise. // - Flush loses its children but remains, because we want to know when a // flushed SetLocal's value is no longer needed. This also makes it simpler // to reason about the format of a local, since we can just do a backwards // analysis (see FlushLivenessAnalysisPhase). As part of the backwards // analysis, we say that the type of a local can be either int32, double, // value, or dead. // - PhantomLocal becomes Phantom, and its child is whatever is specified // by variablesAtHead. // - SetArgument turns into GetArgument unless it's a captured variable. // - Upsilons get their children fixed to refer to the true value of that local // at the end of the block. Prior to this loop, Upsilons will refer to // variableAtTail[operand], which may be any of Flush, PhantomLocal, GetLocal, // SetLocal, SetArgument, or Phi. We accomplish this by setting the // replacement pointers of all of those nodes to refer to either // variablesAtHead[operand], or the child of the SetLocal. for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; for (unsigned phiIndex = block->phis.size(); phiIndex--;) { block->phis[phiIndex]->misc.replacement = block->variablesAtHead.operand(block->phis[phiIndex]->local()); } for (unsigned nodeIndex = block->size(); nodeIndex--;) ASSERT(!block->at(nodeIndex)->misc.replacement); for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { Node* node = block->at(nodeIndex); m_graph.performSubstitution(node); switch (node->op()) { case SetLocal: { VariableAccessData* variable = node->variableAccessData(); if (variable->isCaptured() || m_flushedLocalOps.contains(node)) node->mergeFlags(NodeMustGenerate); else node->setOpAndDefaultFlags(Check); node->misc.replacement = node->child1().node(); // Only for Upsilons. break; } case GetLocal: { // It seems tempting to just do forwardPhi(GetLocal), except that we // could have created a new (SSA) Phi, and the GetLocal could still be // referring to an old (CPS) Phi. Uses variablesAtHead to tell us what // to refer to. node->children.reset(); VariableAccessData* variable = node->variableAccessData(); if (variable->isCaptured()) break; node->convertToPhantom(); node->misc.replacement = block->variablesAtHead.operand(variable->local()); break; } case Flush: { node->children.reset(); // This is only for Upsilons. An Upsilon will only refer to a Flush if // there were no SetLocals or GetLocals in the block. node->misc.replacement = block->variablesAtHead.operand(node->local()); break; } case PhantomLocal: { VariableAccessData* variable = node->variableAccessData(); if (variable->isCaptured()) break; node->child1().setNode(block->variablesAtHead.operand(variable->local())); node->convertToPhantom(); // This is only for Upsilons. An Upsilon will only refer to a // PhantomLocal if there were no SetLocals or GetLocals in the block. node->misc.replacement = block->variablesAtHead.operand(variable->local()); break; } case SetArgument: { VariableAccessData* variable = node->variableAccessData(); if (variable->isCaptured()) break; node->setOpAndDefaultFlags(GetArgument); node->mergeFlags(resultFor(node->variableAccessData()->flushFormat())); break; } default: break; } } } // Free all CPS phis and reset variables vectors. for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; for (unsigned phiIndex = block->phis.size(); phiIndex--;) m_graph.m_allocator.free(block->phis[phiIndex]); block->phis.clear(); block->variablesAtHead.clear(); block->variablesAtTail.clear(); block->valuesAtHead.clear(); block->valuesAtHead.clear(); block->ssa = adoptPtr(new BasicBlock::SSAData(block)); } m_graph.m_arguments.clear(); m_graph.m_form = SSA; return true; }