summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/dfg/DFGPutStackSinkingPhase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/JavaScriptCore/dfg/DFGPutStackSinkingPhase.cpp')
-rw-r--r--Source/JavaScriptCore/dfg/DFGPutStackSinkingPhase.cpp593
1 files changed, 593 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/dfg/DFGPutStackSinkingPhase.cpp b/Source/JavaScriptCore/dfg/DFGPutStackSinkingPhase.cpp
new file mode 100644
index 000000000..6b0bb0763
--- /dev/null
+++ b/Source/JavaScriptCore/dfg/DFGPutStackSinkingPhase.cpp
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2014, 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
+ * 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 "DFGPutStackSinkingPhase.h"
+
+#if ENABLE(DFG_JIT)
+
+#include "DFGBlockMapInlines.h"
+#include "DFGGraph.h"
+#include "DFGInsertionSet.h"
+#include "DFGPhase.h"
+#include "DFGPreciseLocalClobberize.h"
+#include "DFGSSACalculator.h"
+#include "DFGValidate.h"
+#include "JSCInlines.h"
+#include "OperandsInlines.h"
+
+namespace JSC { namespace DFG {
+
+namespace {
+
+bool verbose = false;
+
+class PutStackSinkingPhase : public Phase {
+public:
+ PutStackSinkingPhase(Graph& graph)
+ : Phase(graph, "PutStack sinking")
+ {
+ }
+
+ bool run()
+ {
+ // FIXME: One of the problems of this approach is that it will create a duplicate Phi graph
+ // for sunken PutStacks in the presence of interesting control flow merges, and where the
+ // value being PutStack'd is also otherwise live in the DFG code. We could work around this
+ // by doing the sinking over CPS, or maybe just by doing really smart hoisting. It's also
+ // possible that the duplicate Phi graph can be deduplicated by B3. It would be best if we
+ // could observe that there is already a Phi graph in place that does what we want. In
+ // principle if we have a request to place a Phi at a particular place, we could just check
+ // if there is already a Phi that does what we want. Because PutStackSinkingPhase runs just
+ // after SSA conversion, we have almost a guarantee that the Phi graph we produce here would
+ // be trivially redundant to the one we already have.
+
+ // FIXME: This phase doesn't adequately use KillStacks. KillStack can be viewed as a def.
+ // This is mostly inconsequential; it would be a bug to have a local live at a KillStack.
+ // More important is that KillStack should swallow any deferral. After a KillStack, the
+ // local should behave like a TOP deferral because it would be invalid for anyone to trust
+ // the stack. It's not clear to me if this is important or not.
+ // https://bugs.webkit.org/show_bug.cgi?id=145296
+
+ if (verbose) {
+ dataLog("Graph before PutStack sinking:\n");
+ m_graph.dump();
+ }
+
+ m_graph.ensureDominators();
+
+ SSACalculator ssaCalculator(m_graph);
+ InsertionSet insertionSet(m_graph);
+
+ // First figure out where various locals are live.
+ BlockMap<Operands<bool>> liveAtHead(m_graph);
+ BlockMap<Operands<bool>> liveAtTail(m_graph);
+
+ for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
+ liveAtHead[block] = Operands<bool>(OperandsLike, block->variablesAtHead);
+ liveAtTail[block] = Operands<bool>(OperandsLike, block->variablesAtHead);
+
+ liveAtHead[block].fill(false);
+ liveAtTail[block].fill(false);
+ }
+
+ bool changed;
+ do {
+ changed = false;
+
+ for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
+ BasicBlock* block = m_graph.block(blockIndex);
+ if (!block)
+ continue;
+
+ Operands<bool> live = liveAtTail[block];
+ for (unsigned nodeIndex = block->size(); nodeIndex--;) {
+ Node* node = block->at(nodeIndex);
+ if (verbose)
+ dataLog("Live at ", node, ": ", live, "\n");
+
+ Vector<VirtualRegister, 4> reads;
+ Vector<VirtualRegister, 4> writes;
+ auto escapeHandler = [&] (VirtualRegister operand) {
+ if (operand.isHeader())
+ return;
+ if (verbose)
+ dataLog(" ", operand, " is live at ", node, "\n");
+ reads.append(operand);
+ };
+
+ auto writeHandler = [&] (VirtualRegister operand) {
+ RELEASE_ASSERT(node->op() == PutStack || node->op() == LoadVarargs || node->op() == ForwardVarargs);
+ writes.append(operand);
+ };
+
+ preciseLocalClobberize(
+ m_graph, node, escapeHandler, writeHandler,
+ [&] (VirtualRegister, LazyNode) { });
+
+ for (VirtualRegister operand : writes)
+ live.operand(operand) = false;
+ for (VirtualRegister operand : reads)
+ live.operand(operand) = true;
+ }
+
+ if (live == liveAtHead[block])
+ continue;
+
+ liveAtHead[block] = live;
+ changed = true;
+
+ for (BasicBlock* predecessor : block->predecessors) {
+ for (size_t i = live.size(); i--;)
+ liveAtTail[predecessor][i] |= live[i];
+ }
+ }
+
+ } while (changed);
+
+ // All of the arguments should be live at head of root. Note that we may find that some
+ // locals are live at head of root. This seems wrong but isn't. This will happen for example
+ // if the function accesses closure variable #42 for some other function and we either don't
+ // have variable #42 at all or we haven't set it at root, for whatever reason. Basically this
+ // arises since our aliasing for closure variables is conservatively based on variable number
+ // and ignores the owning symbol table. We should probably fix this eventually and make our
+ // aliasing more precise.
+ //
+ // For our purposes here, the imprecision in the aliasing is harmless. It just means that we
+ // may not do as much Phi pruning as we wanted.
+ for (size_t i = liveAtHead.atIndex(0).numberOfArguments(); i--;)
+ DFG_ASSERT(m_graph, nullptr, liveAtHead.atIndex(0).argument(i));
+
+ // Next identify where we would want to sink PutStacks to. We say that there is a deferred
+ // flush if we had a PutStack with a given FlushFormat but it hasn't been materialized yet.
+ // Deferrals have the following lattice; but it's worth noting that the TOP part of the
+ // lattice serves an entirely different purpose than the rest of the lattice: it just means
+ // that we're in a region of code where nobody should have been relying on the value. The
+ // rest of the lattice means that we either have a PutStack that is deferred (i.e. still
+ // needs to be executed) or there isn't one (because we've alraedy executed it).
+ //
+ // Bottom:
+ // Represented as DeadFlush.
+ // Means that all previous PutStacks have been executed so there is nothing deferred.
+ // During merging this is subordinate to the other kinds of deferrals, because it
+ // represents the fact that we've already executed all necessary PutStacks. This implies
+ // that there *had* been some PutStacks that we should have executed.
+ //
+ // Top:
+ // Represented as ConflictingFlush.
+ // Represents the fact that we know, via forward flow, that there isn't any value in the
+ // given local that anyone should have been relying on. This comes into play at the
+ // prologue (because in SSA form at the prologue no local has any value) or when we merge
+ // deferrals for different formats's. A lexical scope in which a local had some semantic
+ // meaning will by this point share the same format; if we had stores from different
+ // lexical scopes that got merged together then we may have a conflicting format. Hence
+ // a conflicting format proves that we're no longer in an area in which the variable was
+ // in scope. Note that this is all approximate and only precise enough to later answer
+ // questions pertinent to sinking. For example, this doesn't always detect when a local
+ // is no longer semantically relevant - we may well have a deferral from inside some
+ // inlined call survive outside of that inlined code, and this is generally OK. In the
+ // worst case it means that we might think that a deferral that is actually dead must
+ // still be executed. But we usually catch that with liveness. Liveness usually catches
+ // such cases, but that's not guaranteed since liveness is conservative.
+ //
+ // What Top does give us is detects situations where we both don't need to care about a
+ // deferral and there is no way that we could reason about it anyway. If we merged
+ // deferrals for different formats then we wouldn't know the format to use. So, we use
+ // Top in that case because that's also a case where we know that we can ignore the
+ // deferral.
+ //
+ // Deferral with a concrete format:
+ // Represented by format values other than DeadFlush or ConflictingFlush.
+ // Represents the fact that the original code would have done a PutStack but we haven't
+ // identified an operation that would have observed that PutStack.
+ //
+ // We need to be precise about liveness in this phase because not doing so
+ // could cause us to insert a PutStack before a node we thought may escape a
+ // value that it doesn't really escape. Sinking this PutStack above such a node may
+ // cause us to insert a GetStack that we forward to the Phi we're feeding into the
+ // sunken PutStack. Inserting such a GetStack could cause us to load garbage and
+ // can confuse the AI to claim untrue things (like that the program will exit when
+ // it really won't).
+ BlockMap<Operands<FlushFormat>> deferredAtHead(m_graph);
+ BlockMap<Operands<FlushFormat>> deferredAtTail(m_graph);
+
+ for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
+ deferredAtHead[block] =
+ Operands<FlushFormat>(OperandsLike, block->variablesAtHead);
+ deferredAtTail[block] =
+ Operands<FlushFormat>(OperandsLike, block->variablesAtHead);
+ }
+
+ for (unsigned local = deferredAtHead.atIndex(0).numberOfLocals(); local--;)
+ deferredAtHead.atIndex(0).local(local) = ConflictingFlush;
+
+ do {
+ changed = false;
+
+ for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
+ Operands<FlushFormat> deferred = deferredAtHead[block];
+
+ for (Node* node : *block) {
+ if (verbose)
+ dataLog("Deferred at ", node, ":", deferred, "\n");
+
+ if (node->op() == GetStack) {
+ // Handle the case that the input doesn't match our requirements. This is
+ // really a bug, but it's a benign one if we simply don't run this phase.
+ // It usually arises because of patterns like:
+ //
+ // if (thing)
+ // PutStack()
+ // ...
+ // if (thing)
+ // GetStack()
+ //
+ // Or:
+ //
+ // if (never happens)
+ // GetStack()
+ //
+ // Because this phase runs early in SSA, it should be sensible to enforce
+ // that no such code pattern has arisen yet. So, when validation is
+ // enabled, we assert that we aren't seeing this. But with validation
+ // disabled we silently let this fly and we just abort this phase.
+ // FIXME: Get rid of all remaining cases of conflicting GetStacks.
+ // https://bugs.webkit.org/show_bug.cgi?id=150398
+
+ bool isConflicting =
+ deferred.operand(node->stackAccessData()->local) == ConflictingFlush;
+
+ if (validationEnabled())
+ DFG_ASSERT(m_graph, node, !isConflicting);
+
+ if (isConflicting) {
+ // Oh noes! Abort!!
+ return false;
+ }
+
+ // A GetStack doesn't affect anything, since we know which local we are reading
+ // from.
+ continue;
+ } else if (node->op() == PutStack) {
+ VirtualRegister operand = node->stackAccessData()->local;
+ deferred.operand(operand) = node->stackAccessData()->format;
+ continue;
+ }
+
+ auto escapeHandler = [&] (VirtualRegister operand) {
+ if (verbose)
+ dataLog("For ", node, " escaping ", operand, "\n");
+ if (operand.isHeader())
+ return;
+ // We will materialize just before any reads.
+ deferred.operand(operand) = DeadFlush;
+ };
+
+ auto writeHandler = [&] (VirtualRegister operand) {
+ RELEASE_ASSERT(node->op() == LoadVarargs || node->op() == ForwardVarargs);
+ deferred.operand(operand) = DeadFlush;
+ };
+
+ preciseLocalClobberize(
+ m_graph, node, escapeHandler, writeHandler,
+ [&] (VirtualRegister, LazyNode) { });
+ }
+
+ if (deferred == deferredAtTail[block])
+ continue;
+
+ deferredAtTail[block] = deferred;
+ changed = true;
+
+ for (BasicBlock* successor : block->successors()) {
+ for (size_t i = deferred.size(); i--;) {
+ if (verbose)
+ dataLog("Considering ", VirtualRegister(deferred.operandForIndex(i)), " at ", pointerDump(block), "->", pointerDump(successor), ": ", deferred[i], " and ", deferredAtHead[successor][i], " merges to ");
+
+ deferredAtHead[successor][i] =
+ merge(deferredAtHead[successor][i], deferred[i]);
+
+ if (verbose)
+ dataLog(deferredAtHead[successor][i], "\n");
+ }
+ }
+ }
+
+ } while (changed);
+
+ // We wish to insert PutStacks at all of the materialization points, which are defined
+ // implicitly as the places where we set deferred to Dead while it was previously not Dead.
+ // To do this, we may need to build some Phi functions to handle stuff like this:
+ //
+ // Before:
+ //
+ // if (p)
+ // PutStack(r42, @x)
+ // else
+ // PutStack(r42, @y)
+ //
+ // After:
+ //
+ // if (p)
+ // Upsilon(@x, ^z)
+ // else
+ // Upsilon(@y, ^z)
+ // z: Phi()
+ // PutStack(r42, @z)
+ //
+ // This means that we have an SSACalculator::Variable for each local, and a Def is any
+ // PutStack in the original program. The original PutStacks will simply vanish.
+
+ Operands<SSACalculator::Variable*> operandToVariable(
+ OperandsLike, m_graph.block(0)->variablesAtHead);
+ Vector<VirtualRegister> indexToOperand;
+ for (size_t i = m_graph.block(0)->variablesAtHead.size(); i--;) {
+ VirtualRegister operand(m_graph.block(0)->variablesAtHead.operandForIndex(i));
+
+ SSACalculator::Variable* variable = ssaCalculator.newVariable();
+ operandToVariable.operand(operand) = variable;
+ ASSERT(indexToOperand.size() == variable->index());
+ indexToOperand.append(operand);
+ }
+
+ HashSet<Node*> putStacksToSink;
+
+ for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
+ for (Node* node : *block) {
+ switch (node->op()) {
+ case PutStack:
+ putStacksToSink.add(node);
+ ssaCalculator.newDef(
+ operandToVariable.operand(node->stackAccessData()->local),
+ block, node->child1().node());
+ break;
+ case GetStack:
+ ssaCalculator.newDef(
+ operandToVariable.operand(node->stackAccessData()->local),
+ block, node);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ ssaCalculator.computePhis(
+ [&] (SSACalculator::Variable* variable, BasicBlock* block) -> Node* {
+ VirtualRegister operand = indexToOperand[variable->index()];
+
+ if (!liveAtHead[block].operand(operand))
+ return nullptr;
+
+ FlushFormat format = deferredAtHead[block].operand(operand);
+
+ // We could have an invalid deferral because liveness is imprecise.
+ if (!isConcrete(format))
+ return nullptr;
+
+ if (verbose)
+ dataLog("Adding Phi for ", operand, " at ", pointerDump(block), "\n");
+
+ Node* phiNode = m_graph.addNode(SpecHeapTop, Phi, block->at(0)->origin.withInvalidExit());
+ phiNode->mergeFlags(resultFor(format));
+ return phiNode;
+ });
+
+ Operands<Node*> mapping(OperandsLike, m_graph.block(0)->variablesAtHead);
+ Operands<FlushFormat> deferred;
+ for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
+ mapping.fill(nullptr);
+
+ for (size_t i = mapping.size(); i--;) {
+ VirtualRegister operand(mapping.operandForIndex(i));
+
+ SSACalculator::Variable* variable = operandToVariable.operand(operand);
+ SSACalculator::Def* def = ssaCalculator.reachingDefAtHead(block, variable);
+ if (!def)
+ continue;
+
+ mapping.operand(operand) = def->value();
+ }
+
+ if (verbose)
+ dataLog("Mapping at top of ", pointerDump(block), ": ", mapping, "\n");
+
+ for (SSACalculator::Def* phiDef : ssaCalculator.phisForBlock(block)) {
+ VirtualRegister operand = indexToOperand[phiDef->variable()->index()];
+
+ insertionSet.insert(0, phiDef->value());
+
+ if (verbose)
+ dataLog(" Mapping ", operand, " to ", phiDef->value(), "\n");
+ mapping.operand(operand) = phiDef->value();
+ }
+
+ deferred = deferredAtHead[block];
+ for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
+ Node* node = block->at(nodeIndex);
+ if (verbose)
+ dataLog("Deferred at ", node, ":", deferred, "\n");
+
+ switch (node->op()) {
+ case PutStack: {
+ StackAccessData* data = node->stackAccessData();
+ VirtualRegister operand = data->local;
+ deferred.operand(operand) = data->format;
+ if (verbose)
+ dataLog(" Mapping ", operand, " to ", node->child1().node(), " at ", node, "\n");
+ mapping.operand(operand) = node->child1().node();
+ break;
+ }
+
+ case GetStack: {
+ StackAccessData* data = node->stackAccessData();
+ FlushFormat format = deferred.operand(data->local);
+ if (!isConcrete(format)) {
+ DFG_ASSERT(
+ m_graph, node,
+ deferred.operand(data->local) != ConflictingFlush);
+
+ // This means there is no deferral. No deferral means that the most
+ // authoritative value for this stack slot is what is stored in the stack. So,
+ // keep the GetStack.
+ mapping.operand(data->local) = node;
+ break;
+ }
+
+ // We have a concrete deferral, which means a PutStack that hasn't executed yet. It
+ // would have stored a value with a certain format. That format must match our
+ // format. But more importantly, we can simply use the value that the PutStack would
+ // have stored and get rid of the GetStack.
+ DFG_ASSERT(m_graph, node, format == data->format);
+
+ Node* incoming = mapping.operand(data->local);
+ node->child1() = incoming->defaultEdge();
+ node->convertToIdentity();
+ break;
+ }
+
+ default: {
+ auto escapeHandler = [&] (VirtualRegister operand) {
+ if (verbose)
+ dataLog("For ", node, " escaping ", operand, "\n");
+
+ if (operand.isHeader())
+ return;
+
+ FlushFormat format = deferred.operand(operand);
+ if (!isConcrete(format)) {
+ // It's dead now, rather than conflicting.
+ deferred.operand(operand) = DeadFlush;
+ return;
+ }
+
+ // Gotta insert a PutStack.
+ if (verbose)
+ dataLog("Inserting a PutStack for ", operand, " at ", node, "\n");
+
+ Node* incoming = mapping.operand(operand);
+ DFG_ASSERT(m_graph, node, incoming);
+
+ insertionSet.insertNode(
+ nodeIndex, SpecNone, PutStack, node->origin,
+ OpInfo(m_graph.m_stackAccessData.add(operand, format)),
+ Edge(incoming, uncheckedUseKindFor(format)));
+
+ deferred.operand(operand) = DeadFlush;
+ };
+
+ auto writeHandler = [&] (VirtualRegister operand) {
+ // LoadVarargs and ForwardVarargs are unconditional writes to the stack
+ // locations they claim to write to. They do not read from the stack
+ // locations they write to. This makes those stack locations dead right
+ // before a LoadVarargs/ForwardVarargs. This means we should never sink
+ // PutStacks right to this point.
+ RELEASE_ASSERT(node->op() == LoadVarargs || node->op() == ForwardVarargs);
+ deferred.operand(operand) = DeadFlush;
+ };
+
+ preciseLocalClobberize(
+ m_graph, node, escapeHandler, writeHandler,
+ [&] (VirtualRegister, LazyNode) { });
+ break;
+ } }
+ }
+
+ NodeAndIndex terminal = block->findTerminal();
+ size_t upsilonInsertionPoint = terminal.index;
+ NodeOrigin upsilonOrigin = terminal.node->origin;
+ for (BasicBlock* successorBlock : block->successors()) {
+ for (SSACalculator::Def* phiDef : ssaCalculator.phisForBlock(successorBlock)) {
+ Node* phiNode = phiDef->value();
+ SSACalculator::Variable* variable = phiDef->variable();
+ VirtualRegister operand = indexToOperand[variable->index()];
+ if (verbose)
+ dataLog("Creating Upsilon for ", operand, " at ", pointerDump(block), "->", pointerDump(successorBlock), "\n");
+ FlushFormat format = deferredAtHead[successorBlock].operand(operand);
+ DFG_ASSERT(m_graph, nullptr, isConcrete(format));
+ UseKind useKind = uncheckedUseKindFor(format);
+
+ // We need to get a value for the stack slot. This phase doesn't really have a
+ // good way of determining if a stack location got clobbered. It just knows if
+ // there is a deferral. The lack of a deferral might mean that a PutStack or
+ // GetStack had never happened, or it might mean that the value was read, or
+ // that it was written. It's OK for us to make some bad decisions here, since
+ // GCSE will clean it up anyway.
+ Node* incoming;
+ if (isConcrete(deferred.operand(operand))) {
+ incoming = mapping.operand(operand);
+ DFG_ASSERT(m_graph, phiNode, incoming);
+ } else {
+ // Issue a GetStack to get the value. This might introduce some redundancy
+ // into the code, but if it's bad enough, GCSE will clean it up.
+ incoming = insertionSet.insertNode(
+ upsilonInsertionPoint, SpecNone, GetStack, upsilonOrigin,
+ OpInfo(m_graph.m_stackAccessData.add(operand, format)));
+ incoming->setResult(resultFor(format));
+ }
+
+ insertionSet.insertNode(
+ upsilonInsertionPoint, SpecNone, Upsilon, upsilonOrigin,
+ OpInfo(phiNode), Edge(incoming, useKind));
+ }
+ }
+
+ insertionSet.execute(block);
+ }
+
+ // Finally eliminate the sunken PutStacks by turning them into Checks. This keeps whatever
+ // type check they were doing.
+ for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
+ for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
+ Node* node = block->at(nodeIndex);
+
+ if (!putStacksToSink.contains(node))
+ continue;
+
+ node->remove();
+ }
+ }
+
+ if (verbose) {
+ dataLog("Graph after PutStack sinking:\n");
+ m_graph.dump();
+ }
+
+ return true;
+ }
+};
+
+} // anonymous namespace
+
+bool performPutStackSinking(Graph& graph)
+{
+ SamplingRegion samplingRegion("DFG PutStack Sinking Phase");
+ return runPhase<PutStackSinkingPhase>(graph);
+}
+
+} } // namespace JSC::DFG
+
+#endif // ENABLE(DFG_JIT)
+