void JITCompiler::noticeOSREntry(BasicBlock& basicBlock, JITCompiler::Label blockHead, LinkBuffer& linkBuffer) { // OSR entry is not allowed into blocks deemed unreachable by control flow analysis. if (!basicBlock.intersectionOfCFAHasVisited) return; OSREntryData* entry = m_jitCode->appendOSREntryData(basicBlock.bytecodeBegin, linkBuffer.offsetOf(blockHead)); entry->m_expectedValues = basicBlock.intersectionOfPastValuesAtHead; // Fix the expected values: in our protocol, a dead variable will have an expected // value of (None, []). But the old JIT may stash some values there. So we really // need (Top, TOP). for (size_t argument = 0; argument < basicBlock.variablesAtHead.numberOfArguments(); ++argument) { Node* node = basicBlock.variablesAtHead.argument(argument); if (!node || !node->shouldGenerate()) entry->m_expectedValues.argument(argument).makeHeapTop(); } for (size_t local = 0; local < basicBlock.variablesAtHead.numberOfLocals(); ++local) { Node* node = basicBlock.variablesAtHead.local(local); if (!node || !node->shouldGenerate()) entry->m_expectedValues.local(local).makeHeapTop(); else { VariableAccessData* variable = node->variableAccessData(); entry->m_machineStackUsed.set(variable->machineLocal().toLocal()); switch (variable->flushFormat()) { case FlushedDouble: entry->m_localsForcedDouble.set(local); break; case FlushedInt52: entry->m_localsForcedMachineInt.set(local); break; default: break; } if (variable->local() != variable->machineLocal()) { entry->m_reshufflings.append( OSREntryReshuffling( variable->local().offset(), variable->machineLocal().offset())); } } } entry->m_reshufflings.shrinkToFit(); }
bool run() { RELEASE_ASSERT(m_graph.m_form == ThreadedCPS); if (dumpGraph) { dataLog("Graph dump at top of SSA conversion:\n"); m_graph.dump(); } // 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 = !!(node->flags() & NodeIsFlushed); 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 and turns into a Phantom. // - 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() || !!(node->flags() & NodeIsFlushed)) 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(); node->convertToPhantom(); // 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; }
bool run() { // This enumerates the locals that we actually care about and packs them. So for example // if we use local 1, 3, 4, 5, 7, then we remap them: 1->0, 3->1, 4->2, 5->3, 7->4. We // treat a variable as being "used" if there exists an access to it (SetLocal, GetLocal, // Flush, PhantomLocal). BitVector usedLocals; // Collect those variables that are used from IR. bool hasNodesThatNeedFixup = false; 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); switch (node->op()) { case GetLocal: case SetLocal: case Flush: case PhantomLocal: { VariableAccessData* variable = node->variableAccessData(); if (variable->local().isArgument()) break; usedLocals.set(variable->local().toLocal()); break; } case GetLocalUnlinked: { VirtualRegister operand = node->unlinkedLocal(); if (operand.isArgument()) break; usedLocals.set(operand.toLocal()); hasNodesThatNeedFixup = true; break; } case LoadVarargs: case ForwardVarargs: { LoadVarargsData* data = node->loadVarargsData(); if (data->count.isLocal()) usedLocals.set(data->count.toLocal()); if (data->start.isLocal()) { // This part really relies on the contiguity of stack layout // assignments. ASSERT(VirtualRegister(data->start.offset() + data->limit - 1).isLocal()); for (unsigned i = data->limit; i--;) usedLocals.set(VirtualRegister(data->start.offset() + i).toLocal()); } // the else case shouldn't happen. hasNodesThatNeedFixup = true; break; } case PutStack: case GetStack: { StackAccessData* stack = node->stackAccessData(); if (stack->local.isArgument()) break; usedLocals.set(stack->local.toLocal()); break; } default: break; } } } for (InlineCallFrameSet::iterator iter = m_graph.m_plan.inlineCallFrames->begin(); !!iter; ++iter) { InlineCallFrame* inlineCallFrame = *iter; if (inlineCallFrame->isVarargs()) { usedLocals.set(VirtualRegister( JSStack::ArgumentCount + inlineCallFrame->stackOffset).toLocal()); } for (unsigned argument = inlineCallFrame->arguments.size(); argument-- > 1;) { usedLocals.set(VirtualRegister( virtualRegisterForArgument(argument).offset() + inlineCallFrame->stackOffset).toLocal()); } } Vector<unsigned> allocation(usedLocals.size()); m_graph.m_nextMachineLocal = 0; for (unsigned i = 0; i < usedLocals.size(); ++i) { if (!usedLocals.get(i)) { allocation[i] = UINT_MAX; continue; } allocation[i] = m_graph.m_nextMachineLocal++; } for (unsigned i = m_graph.m_variableAccessData.size(); i--;) { VariableAccessData* variable = &m_graph.m_variableAccessData[i]; if (!variable->isRoot()) continue; if (variable->local().isArgument()) { variable->machineLocal() = variable->local(); continue; } size_t local = variable->local().toLocal(); if (local >= allocation.size()) continue; if (allocation[local] == UINT_MAX) continue; variable->machineLocal() = assign(allocation, variable->local()); } for (StackAccessData* data : m_graph.m_stackAccessData) { if (!data->local.isLocal()) { data->machineLocal = data->local; continue; } if (static_cast<size_t>(data->local.toLocal()) >= allocation.size()) continue; if (allocation[data->local.toLocal()] == UINT_MAX) continue; data->machineLocal = assign(allocation, data->local); } // This register is never valid for DFG code blocks. codeBlock()->setActivationRegister(VirtualRegister()); if (LIKELY(!m_graph.hasDebuggerEnabled())) codeBlock()->setScopeRegister(VirtualRegister()); else codeBlock()->setScopeRegister(assign(allocation, codeBlock()->scopeRegister())); for (unsigned i = m_graph.m_inlineVariableData.size(); i--;) { InlineVariableData data = m_graph.m_inlineVariableData[i]; InlineCallFrame* inlineCallFrame = data.inlineCallFrame; if (inlineCallFrame->isVarargs()) { inlineCallFrame->argumentCountRegister = assign( allocation, VirtualRegister(inlineCallFrame->stackOffset + JSStack::ArgumentCount)); } for (unsigned argument = inlineCallFrame->arguments.size(); argument-- > 1;) { ArgumentPosition& position = m_graph.m_argumentPositions[ data.argumentPositionStart + argument]; VariableAccessData* variable = position.someVariable(); ValueSource source; if (!variable) source = ValueSource(SourceIsDead); else { source = ValueSource::forFlushFormat( variable->machineLocal(), variable->flushFormat()); } inlineCallFrame->arguments[argument] = source.valueRecovery(); } RELEASE_ASSERT(inlineCallFrame->isClosureCall == !!data.calleeVariable); if (inlineCallFrame->isClosureCall) { VariableAccessData* variable = data.calleeVariable->find(); ValueSource source = ValueSource::forFlushFormat( variable->machineLocal(), variable->flushFormat()); inlineCallFrame->calleeRecovery = source.valueRecovery(); } else RELEASE_ASSERT(inlineCallFrame->calleeRecovery.isConstant()); } // Fix GetLocalUnlinked's variable references. if (hasNodesThatNeedFixup) { 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); switch (node->op()) { case GetLocalUnlinked: { node->setUnlinkedMachineLocal(assign(allocation, node->unlinkedLocal())); break; } case LoadVarargs: case ForwardVarargs: { LoadVarargsData* data = node->loadVarargsData(); data->machineCount = assign(allocation, data->count); data->machineStart = assign(allocation, data->start); break; } default: break; } } } } return true; }
bool run() { RELEASE_ASSERT(m_graph.m_form == ThreadedCPS); m_graph.clearReplacements(); m_graph.m_dominators.computeIfNecessary(m_graph); if (verbose) { dataLog("Graph before SSA transformation:\n"); m_graph.dump(); } // Create a SSACalculator::Variable for every root VariableAccessData. for (VariableAccessData& variable : m_graph.m_variableAccessData) { if (!variable.isRoot() || variable.isCaptured()) continue; SSACalculator::Variable* ssaVariable = m_calculator.newVariable(); ASSERT(ssaVariable->index() == m_variableForSSAIndex.size()); m_variableForSSAIndex.append(&variable); m_ssaVariableForVariable.add(&variable, ssaVariable); } // Find all SetLocals and create Defs for them. We handle SetArgument by creating a // GetLocal, and recording the flush format. for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { BasicBlock* block = m_graph.block(blockIndex); if (!block) continue; // Must process the block in forward direction because we want to see the last // assignment for every local. for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { Node* node = block->at(nodeIndex); if (node->op() != SetLocal && node->op() != SetArgument) continue; VariableAccessData* variable = node->variableAccessData(); if (variable->isCaptured()) continue; Node* childNode; if (node->op() == SetLocal) childNode = node->child1().node(); else { ASSERT(node->op() == SetArgument); childNode = m_insertionSet.insertNode( nodeIndex, node->variableAccessData()->prediction(), GetStack, node->origin, OpInfo(m_graph.m_stackAccessData.add(variable->local(), variable->flushFormat()))); if (!ASSERT_DISABLED) m_argumentGetters.add(childNode); m_argumentMapping.add(node, childNode); } m_calculator.newDef( m_ssaVariableForVariable.get(variable), block, childNode); } m_insertionSet.execute(block); } // Decide where Phis are to be inserted. This creates the Phi's but doesn't insert them // yet. We will later know where to insert them because SSACalculator is such a bro. m_calculator.computePhis( [&] (SSACalculator::Variable* ssaVariable, BasicBlock* block) -> Node* { VariableAccessData* variable = m_variableForSSAIndex[ssaVariable->index()]; // Prune by liveness. This doesn't buy us much other than compile times. Node* headNode = block->variablesAtHead.operand(variable->local()); if (!headNode) return nullptr; // There is the possibiltiy of "rebirths". The SSA calculator will already prune // rebirths for the same VariableAccessData. But it will not be able to prune // rebirths that arose from the same local variable number but a different // VariableAccessData. We do that pruning here. // // Here's an example of a rebirth that this would catch: // // var x; // if (foo) { // if (bar) { // x = 42; // } else { // x = 43; // } // print(x); // x = 44; // } else { // x = 45; // } // print(x); // Without this check, we'd have a Phi for x = 42|43 here. // // FIXME: Consider feeding local variable numbers, not VariableAccessData*'s, as // the "variables" for SSACalculator. That would allow us to eliminate this // special case. // https://bugs.webkit.org/show_bug.cgi?id=136641 if (headNode->variableAccessData() != variable) return nullptr; Node* phiNode = m_graph.addNode( variable->prediction(), Phi, NodeOrigin()); FlushFormat format = variable->flushFormat(); NodeFlags result = resultFor(format); phiNode->mergeFlags(result); return phiNode; }); if (verbose) { dataLog("Computed Phis, about to transform the graph.\n"); dataLog("\n"); dataLog("Graph:\n"); m_graph.dump(); dataLog("\n"); dataLog("Mappings:\n"); for (unsigned i = 0; i < m_variableForSSAIndex.size(); ++i) dataLog(" ", i, ": ", VariableAccessDataDump(m_graph, m_variableForSSAIndex[i]), "\n"); dataLog("\n"); dataLog("SSA calculator: ", m_calculator, "\n"); } // Do the bulk of the SSA conversion. For each block, this tracks the operand->Node // mapping based on a combination of what the SSACalculator tells us, and us walking over // the block in forward order. We use our own data structure, valueForOperand, for // determining the local mapping, but we rely on SSACalculator for the non-local mapping. // // This does three things at once: // // - Inserts the Phis in all of the places where they need to go. We've already created // them and they are accounted for in the SSACalculator's data structures, but we // haven't inserted them yet, mostly because we want to insert all of a block's Phis in // one go to amortize the cost of node insertion. // // - Create and insert Upsilons. // // - Convert all of the preexisting SSA nodes (other than the old CPS Phi nodes) into SSA // form by replacing as follows: // // - MovHint has KillLocal prepended to it. // // - GetLocal over captured variables lose their phis and become GetStack. // // - GetLocal over uncaptured variables die and get replaced with references to the node // specified by valueForOperand. // // - SetLocal turns into PutStack if it's flushed, or turns into a Check otherwise. // // - Flush loses its children and turns into a Phantom. // // - PhantomLocal becomes Phantom, and its child is whatever is specified by // valueForOperand. // // - SetArgument is removed. Note that GetStack nodes have already been inserted. Operands<Node*> valueForOperand(OperandsLike, m_graph.block(0)->variablesAtHead); for (BasicBlock* block : m_graph.blocksInPreOrder()) { valueForOperand.clear(); // CPS will claim that the root block has all arguments live. But we have already done // the first step of SSA conversion: argument locals are no longer live at head; // instead we have GetStack nodes for extracting the values of arguments. So, we // skip the at-head available value calculation for the root block. if (block != m_graph.block(0)) { for (size_t i = valueForOperand.size(); i--;) { Node* nodeAtHead = block->variablesAtHead[i]; if (!nodeAtHead) continue; VariableAccessData* variable = nodeAtHead->variableAccessData(); if (variable->isCaptured()) continue; if (verbose) dataLog("Considering live variable ", VariableAccessDataDump(m_graph, variable), " at head of block ", *block, "\n"); SSACalculator::Variable* ssaVariable = m_ssaVariableForVariable.get(variable); SSACalculator::Def* def = m_calculator.reachingDefAtHead(block, ssaVariable); if (!def) { // If we are required to insert a Phi, then we won't have a reaching def // at head. continue; } Node* node = def->value(); if (node->replacement) { // This will occur when a SetLocal had a GetLocal as its source. The // GetLocal would get replaced with an actual SSA value by the time we get // here. Note that the SSA value with which the GetLocal got replaced // would not in turn have a replacement. node = node->replacement; ASSERT(!node->replacement); } if (verbose) dataLog("Mapping: ", VirtualRegister(valueForOperand.operandForIndex(i)), " -> ", node, "\n"); valueForOperand[i] = node; } } // Insert Phis by asking the calculator what phis there are in this block. Also update // valueForOperand with those Phis. For Phis associated with variables that are not // flushed, we also insert a MovHint. size_t phiInsertionPoint = 0; for (SSACalculator::Def* phiDef : m_calculator.phisForBlock(block)) { VariableAccessData* variable = m_variableForSSAIndex[phiDef->variable()->index()]; m_insertionSet.insert(phiInsertionPoint, phiDef->value()); valueForOperand.operand(variable->local()) = phiDef->value(); m_insertionSet.insertNode( phiInsertionPoint, SpecNone, MovHint, NodeOrigin(), OpInfo(variable->local().offset()), phiDef->value()->defaultEdge()); } for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { Node* node = block->at(nodeIndex); if (verbose) { dataLog("Processing node ", node, ":\n"); m_graph.dump(WTF::dataFile(), " ", node); } m_graph.performSubstitution(node); switch (node->op()) { case MovHint: { m_insertionSet.insertNode( nodeIndex, SpecNone, KillStack, node->origin, OpInfo(node->unlinkedLocal().offset())); break; } case SetLocal: { VariableAccessData* variable = node->variableAccessData(); if (variable->isCaptured() || !!(node->flags() & NodeIsFlushed)) { node->convertToPutStack( m_graph.m_stackAccessData.add( variable->local(), variable->flushFormat())); } else node->setOpAndDefaultFlags(Check); if (!variable->isCaptured()) { if (verbose) dataLog("Mapping: ", variable->local(), " -> ", node->child1().node(), "\n"); valueForOperand.operand(variable->local()) = node->child1().node(); } break; } case GetStack: { ASSERT(m_argumentGetters.contains(node)); valueForOperand.operand(node->stackAccessData()->local) = node; break; } case GetLocal: { VariableAccessData* variable = node->variableAccessData(); node->children.reset(); if (variable->isCaptured()) { node->convertToGetStack(m_graph.m_stackAccessData.add(variable->local(), variable->flushFormat())); break; } node->convertToPhantom(); if (verbose) dataLog("Replacing node ", node, " with ", valueForOperand.operand(variable->local()), "\n"); node->replacement = valueForOperand.operand(variable->local()); break; } case Flush: { node->children.reset(); node->convertToPhantom(); break; } case PhantomLocal: { ASSERT(node->child1().useKind() == UntypedUse); VariableAccessData* variable = node->variableAccessData(); if (variable->isCaptured()) { // This is a fun case. We could have a captured variable that had some // or all of its uses strength reduced to phantoms rather than flushes. // SSA conversion will currently still treat it as flushed, in the sense // that it will just keep the SetLocal. Therefore, there is nothing that // needs to be done here: we don't need to also keep the source value // alive. And even if we did want to keep the source value alive, we // wouldn't be able to, because the variablesAtHead value for a captured // local wouldn't have been computed by the Phi reduction algorithm // above. node->children.reset(); } else node->child1() = valueForOperand.operand(variable->local())->defaultEdge(); node->convertToPhantom(); break; } case SetArgument: { node->convertToPhantom(); break; } default: break; } } // We want to insert Upsilons just before the end of the block. On the surface this // seems dangerous because the Upsilon will have a checking UseKind. But, we will not // actually be performing the check at the point of the Upsilon; the check will // already have been performed at the point where the original SetLocal was. size_t upsilonInsertionPoint = block->size() - 1; NodeOrigin upsilonOrigin = block->last()->origin; for (unsigned successorIndex = block->numSuccessors(); successorIndex--;) { BasicBlock* successorBlock = block->successor(successorIndex); for (SSACalculator::Def* phiDef : m_calculator.phisForBlock(successorBlock)) { Node* phiNode = phiDef->value(); SSACalculator::Variable* ssaVariable = phiDef->variable(); VariableAccessData* variable = m_variableForSSAIndex[ssaVariable->index()]; FlushFormat format = variable->flushFormat(); UseKind useKind = useKindFor(format); m_insertionSet.insertNode( upsilonInsertionPoint, SpecNone, Upsilon, upsilonOrigin, OpInfo(phiNode), Edge( valueForOperand.operand(variable->local()), useKind)); } } m_insertionSet.execute(block); } // 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 = std::make_unique<BasicBlock::SSAData>(block); } m_graph.m_argumentFormats.resize(m_graph.m_arguments.size()); for (unsigned i = m_graph.m_arguments.size(); i--;) { FlushFormat format = FlushedJSValue; Node* node = m_argumentMapping.get(m_graph.m_arguments[i]); // m_argumentMapping.get could return null for a captured local. That's fine. We only // track the argument loads of those arguments for which we speculate type. We don't // speculate type for captured arguments. if (node) format = node->stackAccessData()->format; m_graph.m_argumentFormats[i] = format; m_graph.m_arguments[i] = node; // Record the load that loads the arguments for the benefit of exit profiling. } m_graph.m_form = SSA; if (verbose) { dataLog("Graph after SSA transformation:\n"); m_graph.dump(); } return true; }
void process(BlockIndex blockIndex) { BasicBlock* block = m_graph.block(blockIndex); if (!block) return; m_live = block->ssa->flushFormatAtTail; for (unsigned nodeIndex = block->size(); nodeIndex--;) { Node* node = block->at(nodeIndex); switch (node->op()) { case SetLocal: { VariableAccessData* variable = node->variableAccessData(); setForNode(node, variable->local(), variable->flushFormat(), DeadFlush); break; } case GetArgument: { VariableAccessData* variable = node->variableAccessData(); setForNode(node, variable->local(), variable->flushFormat(), FlushedJSValue); break; } case Flush: case GetLocal: { VariableAccessData* variable = node->variableAccessData(); FlushFormat format = variable->flushFormat(); setForNode(node, variable->local(), format, format); break; } default: break; } } if (m_live == block->ssa->flushFormatAtHead) return; m_changed = true; block->ssa->flushFormatAtHead = m_live; for (unsigned i = block->predecessors.size(); i--;) { BasicBlock* predecessor = block->predecessors[i]; for (unsigned j = m_live.size(); j--;) { FlushFormat& predecessorFormat = predecessor->ssa->flushFormatAtTail[j]; FlushFormat myFormat = m_live[j]; // Three possibilities: // 1) Predecessor format is Dead, in which case it acquires our format. // 2) Predecessor format is identical to our format, in which case we // do nothing. // 3) Predecessor format is different from our format and it's not Dead, // in which case we have an erroneous set of Flushes and SetLocals. // FIXME: What if the predecessor was already processed by the fixpoint // and says "not Dead" and the current block says "Dead"? We may want to // revisit this, and say that this is is acceptable. if (predecessorFormat == DeadFlush) { predecessorFormat = myFormat; continue; } if (predecessorFormat == myFormat) continue; dataLog( "Bad Flush merge at edge ", *predecessor, " -> ", *block, ", local variable r", m_live.operandForIndex(j), ": ", *predecessor, " has ", predecessorFormat, " and ", *block, " has ", myFormat, ".\n"); dataLog("IR at time of error:\n"); m_graph.dump(); CRASH(); } } }
bool run() { SharedSymbolTable* symbolTable = codeBlock()->symbolTable(); // This enumerates the locals that we actually care about and packs them. So for example // if we use local 1, 3, 4, 5, 7, then we remap them: 1->0, 3->1, 4->2, 5->3, 7->4. We // treat a variable as being "used" if there exists an access to it (SetLocal, GetLocal, // Flush, PhantomLocal). BitVector usedLocals; // Collect those variables that are used from IR. bool hasGetLocalUnlinked = false; 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); switch (node->op()) { case GetLocal: case SetLocal: case Flush: case PhantomLocal: { VariableAccessData* variable = node->variableAccessData(); if (variable->local().isArgument()) break; usedLocals.set(variable->local().toLocal()); break; } case GetLocalUnlinked: { VirtualRegister operand = node->unlinkedLocal(); if (operand.isArgument()) break; usedLocals.set(operand.toLocal()); hasGetLocalUnlinked = true; break; } default: break; } } } // Ensure that captured variables and captured inline arguments are pinned down. // They should have been because of flushes, except that the flushes can be optimized // away. if (symbolTable) { for (int i = symbolTable->captureStart(); i > symbolTable->captureEnd(); i--) usedLocals.set(VirtualRegister(i).toLocal()); } if (codeBlock()->usesArguments()) { usedLocals.set(codeBlock()->argumentsRegister().toLocal()); usedLocals.set(unmodifiedArgumentsRegister(codeBlock()->argumentsRegister()).toLocal()); } if (codeBlock()->uncheckedActivationRegister().isValid()) usedLocals.set(codeBlock()->activationRegister().toLocal()); for (InlineCallFrameSet::iterator iter = m_graph.m_inlineCallFrames->begin(); !!iter; ++iter) { InlineCallFrame* inlineCallFrame = *iter; if (!inlineCallFrame->executable->usesArguments()) continue; VirtualRegister argumentsRegister = m_graph.argumentsRegisterFor(inlineCallFrame); usedLocals.set(argumentsRegister.toLocal()); usedLocals.set(unmodifiedArgumentsRegister(argumentsRegister).toLocal()); for (unsigned argument = inlineCallFrame->arguments.size(); argument-- > 1;) { usedLocals.set(VirtualRegister( virtualRegisterForArgument(argument).offset() + inlineCallFrame->stackOffset).toLocal()); } } Vector<unsigned> allocation(usedLocals.size()); m_graph.m_nextMachineLocal = 0; for (unsigned i = 0; i < usedLocals.size(); ++i) { if (!usedLocals.get(i)) { allocation[i] = UINT_MAX; continue; } allocation[i] = m_graph.m_nextMachineLocal++; } for (unsigned i = m_graph.m_variableAccessData.size(); i--;) { VariableAccessData* variable = &m_graph.m_variableAccessData[i]; if (!variable->isRoot()) continue; if (variable->local().isArgument()) { variable->machineLocal() = variable->local(); continue; } size_t local = variable->local().toLocal(); if (local >= allocation.size()) continue; if (allocation[local] == UINT_MAX) continue; variable->machineLocal() = virtualRegisterForLocal( allocation[variable->local().toLocal()]); } if (codeBlock()->usesArguments()) { VirtualRegister argumentsRegister = virtualRegisterForLocal( allocation[codeBlock()->argumentsRegister().toLocal()]); RELEASE_ASSERT( virtualRegisterForLocal(allocation[ unmodifiedArgumentsRegister( codeBlock()->argumentsRegister()).toLocal()]) == unmodifiedArgumentsRegister(argumentsRegister)); codeBlock()->setArgumentsRegister(argumentsRegister); } if (codeBlock()->uncheckedActivationRegister().isValid()) { codeBlock()->setActivationRegister( virtualRegisterForLocal(allocation[codeBlock()->activationRegister().toLocal()])); } for (unsigned i = m_graph.m_inlineVariableData.size(); i--;) { InlineVariableData data = m_graph.m_inlineVariableData[i]; InlineCallFrame* inlineCallFrame = data.inlineCallFrame; if (inlineCallFrame->executable->usesArguments()) { inlineCallFrame->argumentsRegister = virtualRegisterForLocal( allocation[m_graph.argumentsRegisterFor(inlineCallFrame).toLocal()]); RELEASE_ASSERT( virtualRegisterForLocal(allocation[unmodifiedArgumentsRegister( m_graph.argumentsRegisterFor(inlineCallFrame)).toLocal()]) == unmodifiedArgumentsRegister(inlineCallFrame->argumentsRegister)); } for (unsigned argument = inlineCallFrame->arguments.size(); argument-- > 1;) { ArgumentPosition& position = m_graph.m_argumentPositions[ data.argumentPositionStart + argument]; VariableAccessData* variable = position.someVariable(); ValueSource source; if (!variable) source = ValueSource(SourceIsDead); else { source = ValueSource::forFlushFormat( variable->machineLocal(), variable->flushFormat()); } inlineCallFrame->arguments[argument] = source.valueRecovery(); } RELEASE_ASSERT(inlineCallFrame->isClosureCall == !!data.calleeVariable); if (inlineCallFrame->isClosureCall) { ValueSource source = ValueSource::forFlushFormat( data.calleeVariable->machineLocal(), data.calleeVariable->flushFormat()); inlineCallFrame->calleeRecovery = source.valueRecovery(); } else RELEASE_ASSERT(inlineCallFrame->calleeRecovery.isConstant()); } if (symbolTable) { if (symbolTable->captureCount()) { unsigned captureStartLocal = allocation[ VirtualRegister(codeBlock()->symbolTable()->captureStart()).toLocal()]; ASSERT(captureStartLocal != UINT_MAX); m_graph.m_machineCaptureStart = virtualRegisterForLocal(captureStartLocal).offset(); } else m_graph.m_machineCaptureStart = virtualRegisterForLocal(0).offset(); // This is an abomination. If we had captured an argument then the argument ends // up being "slow", meaning that loads of the argument go through an extra lookup // table. if (const SlowArgument* slowArguments = symbolTable->slowArguments()) { auto newSlowArguments = std::make_unique<SlowArgument[]>( symbolTable->parameterCount()); for (size_t i = symbolTable->parameterCount(); i--;) { newSlowArguments[i] = slowArguments[i]; VirtualRegister reg = VirtualRegister(slowArguments[i].index); if (reg.isLocal()) newSlowArguments[i].index = virtualRegisterForLocal(allocation[reg.toLocal()]).offset(); } m_graph.m_slowArguments = std::move(newSlowArguments); } } // Fix GetLocalUnlinked's variable references. if (hasGetLocalUnlinked) { 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); switch (node->op()) { case GetLocalUnlinked: { VirtualRegister operand = node->unlinkedLocal(); if (operand.isLocal()) operand = virtualRegisterForLocal(allocation[operand.toLocal()]); node->setUnlinkedMachineLocal(operand); break; } default: break; } } } } return true; }