DFGObjectAllocationSinkingPhase.cpp   [plain text]


/*
 * 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 "DFGObjectAllocationSinkingPhase.h"

#if ENABLE(DFG_JIT)

#include "DFGAbstractHeap.h"
#include "DFGBlockMapInlines.h"
#include "DFGClobberize.h"
#include "DFGCombinedLiveness.h"
#include "DFGGraph.h"
#include "DFGInsertOSRHintsForUpdate.h"
#include "DFGInsertionSet.h"
#include "DFGLivenessAnalysisPhase.h"
#include "DFGOSRAvailabilityAnalysisPhase.h"
#include "DFGPhase.h"
#include "DFGPromoteHeapAccess.h"
#include "DFGSSACalculator.h"
#include "DFGValidate.h"
#include "JSCInlines.h"

namespace JSC { namespace DFG {

static bool verbose = false;

class ObjectAllocationSinkingPhase : public Phase {
public:
    ObjectAllocationSinkingPhase(Graph& graph)
        : Phase(graph, "object allocation sinking")
        , m_ssaCalculator(graph)
        , m_insertionSet(graph)
    {
    }
    
    bool run()
    {
        ASSERT(m_graph.m_fixpointState == FixpointNotConverged);
        
        m_graph.m_dominators.computeIfNecessary(m_graph);
        
        // Logically we wish to consider every NewObject and sink it. However it's probably not
        // profitable to sink a NewObject that will always escape. So, first we do a very simple
        // forward flow analysis that determines the set of NewObject nodes that have any chance
        // of benefiting from object allocation sinking. Then we fixpoint the following rules:
        //
        // - For each NewObject, we turn the original NewObject into a PhantomNewObject and then
        //   we insert MaterializeNewObject just before those escaping sites that come before any
        //   other escaping sites - that is, there is no path between the allocation and those sites
        //   that would see any other escape. Note that Upsilons constitute escaping sites. Then we
        //   insert additional MaterializeNewObject nodes on Upsilons that feed into Phis that mix
        //   materializations and the original PhantomNewObject. We then turn each PutByOffset over a
        //   PhantomNewObject into a PutHint.
        //
        // - We perform the same optimization for MaterializeNewObject. This allows us to cover
        //   cases where we had MaterializeNewObject flowing into a PutHint.
        //
        // We could also add this rule:
        //
        // - If all of the Upsilons of a Phi have a MaterializeNewObject that isn't used by anyone
        //   else, then replace the Phi with the MaterializeNewObject.
        //
        //   FIXME: Implement this. Note that this totally doable but it requires some gnarly
        //   code, and to be effective the pruner needs to be aware of it. Currently any Upsilon
        //   is considered to be an escape even by the pruner, so it's unlikely that we'll see
        //   many cases of Phi over Materializations.
        //   https://bugs.webkit.org/show_bug.cgi?id=136927
        
        if (!performSinking())
            return false;
        
        while (performSinking()) { }
        
        if (verbose) {
            dataLog("Graph after sinking:\n");
            m_graph.dump();
        }
        
        return true;
    }

private:
    bool performSinking()
    {
        m_graph.computeRefCounts();
        performLivenessAnalysis(m_graph);
        performOSRAvailabilityAnalysis(m_graph);
        m_combinedLiveness = CombinedLiveness(m_graph);
        
        CString graphBeforeSinking;
        if (Options::verboseValidationFailure() && Options::validateGraphAtEachPhase()) {
            StringPrintStream out;
            m_graph.dump(out);
            graphBeforeSinking = out.toCString();
        }
        
        if (verbose) {
            dataLog("Graph before sinking:\n");
            m_graph.dump();
        }
        
        determineMaterializationPoints();
        if (m_sinkCandidates.isEmpty())
            return false;
        
        // At this point we are committed to sinking the sinking candidates.
        placeMaterializationPoints();
        lowerNonReadingOperationsOnPhantomAllocations();
        promoteSunkenFields();
        
        if (Options::validateGraphAtEachPhase())
            validate(m_graph, DumpGraph, graphBeforeSinking);
        
        if (verbose)
            dataLog("Sinking iteration changed the graph.\n");
        return true;
    }
    
    void determineMaterializationPoints()
    {
        // The premise of this pass is that if there exists a point in the program where some
        // path from a phantom allocation site to that point causes materialization, then *all*
        // paths cause materialization. This should mean that there are never any redundant
        // materializations.
        
        m_sinkCandidates.clear();
        m_materializationToEscapee.clear();
        m_materializationSiteToMaterializations.clear();
        
        BlockMap<HashMap<Node*, bool>> materializedAtHead(m_graph);
        BlockMap<HashMap<Node*, bool>> materializedAtTail(m_graph);
        
        bool changed;
        do {
            if (verbose)
                dataLog("Doing iteration of materialization point placement.\n");
            changed = false;
            for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
                HashMap<Node*, bool> materialized = materializedAtHead[block];
                if (verbose)
                    dataLog("    Looking at block ", pointerDump(block), "\n");
                for (Node* node : *block) {
                    handleNode(
                        node,
                        [&] () {
                            materialized.add(node, false);
                        },
                        [&] (Node* escapee) {
                            auto iter = materialized.find(escapee);
                            if (iter != materialized.end()) {
                                if (verbose)
                                    dataLog("    ", escapee, " escapes at ", node, "\n");
                                iter->value = true;
                            }
                        });
                }
                
                if (verbose)
                    dataLog("    Materialized at tail of ", pointerDump(block), ": ", mapDump(materialized), "\n");
                
                if (materialized == materializedAtTail[block])
                    continue;
                
                materializedAtTail[block] = materialized;
                changed = true;
                
                // Only propagate things to our successors if they are alive in all successors.
                // So, we prune materialized-at-tail to only include things that are live.
                Vector<Node*> toRemove;
                for (auto pair : materialized) {
                    if (!m_combinedLiveness.liveAtTail[block].contains(pair.key))
                        toRemove.append(pair.key);
                }
                for (Node* key : toRemove)
                    materialized.remove(key);
                
                for (BasicBlock* successorBlock : block->successors()) {
                    for (auto pair : materialized) {
                        materializedAtHead[successorBlock].add(
                            pair.key, false).iterator->value |= pair.value;
                    }
                }
            }
        } while (changed);
        
        // Determine the sink candidates. Broadly, a sink candidate is a node that handleNode()
        // believes is sinkable, and one of the following is true:
        //
        // 1) There exists a basic block with only backward outgoing edges (or no outgoing edges)
        //    in which the node wasn't materialized. This is meant to catch effectively-infinite
        //    loops in which we don't need to have allocated the object.
        //
        // 2) There exists a basic block at the tail of which the node is not materialized and the
        //    node is dead.
        //
        // 3) The sum of execution counts of the materializations is less than the sum of
        //    execution counts of the original node.
        //
        // We currently implement only rule #2.
        // FIXME: Implement the two other rules.
        // https://bugs.webkit.org/show_bug.cgi?id=137073 (rule #1)
        // https://bugs.webkit.org/show_bug.cgi?id=137074 (rule #3)
        
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            for (auto pair : materializedAtTail[block]) {
                if (pair.value)
                    continue; // It was materialized.
                
                if (m_combinedLiveness.liveAtTail[block].contains(pair.key))
                    continue; // It might still get materialized in all of the successors.
                
                // We know that it died in this block and it wasn't materialized. That means that
                // if we sink this allocation, then *this* will be a path along which we never
                // have to allocate. Profit!
                m_sinkCandidates.add(pair.key);
            }
        }
        
        if (m_sinkCandidates.isEmpty())
            return;
        
        // A materialization edge exists at any point where a node escapes but hasn't been
        // materialized yet. We do this in two parts. First we find all of the nodes that cause
        // escaping to happen, where the escapee had not yet been materialized. This catches
        // everything but loops. We then catch loops - as well as weirder control flow constructs -
        // in a subsequent pass that looks at places in the CFG where an edge exists from a block
        // that hasn't materialized to a block that has. We insert the materialization along such an
        // edge, and we rely on the fact that critical edges were already broken so that we actually
        // either insert the materialization at the head of the successor or the tail of the
        // predecessor.
        //
        // FIXME: This can create duplicate allocations when we really only needed to perform one.
        // For example:
        //
        //     var o = new Object();
        //     if (rare) {
        //         if (stuff)
        //             call(o); // o escapes here.
        //         return;
        //     }
        //     // o doesn't escape down here.
        //
        // In this example, we would place a materialization point at call(o) and then we would find
        // ourselves having to insert another one at the implicit else case of that if statement
        // ('cause we've broken critical edges). We would instead really like to just have one
        // materialization point right at the top of the then case of "if (rare)". To do this, we can
        // find the LCA of the various materializations in the dom tree.
        // https://bugs.webkit.org/show_bug.cgi?id=137124
        
        // First pass: find intra-block materialization points.
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            HashSet<Node*> materialized;
            for (auto pair : materializedAtHead[block]) {
                if (pair.value && m_sinkCandidates.contains(pair.key))
                    materialized.add(pair.key);
            }
            
            if (verbose)
                dataLog("Placing materialization points in ", pointerDump(block), " with materialization set ", listDump(materialized), "\n");
            
            for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
                Node* node = block->at(nodeIndex);
                
                handleNode(
                    node,
                    [&] () { },
                    [&] (Node* escapee) {
                        if (verbose)
                            dataLog("Seeing ", escapee, " escape at ", node, "\n");
                        
                        if (!m_sinkCandidates.contains(escapee)) {
                            if (verbose)
                                dataLog("    It's not a sink candidate.\n");
                            return;
                        }
                        
                        if (!materialized.add(escapee).isNewEntry) {
                            if (verbose)
                                dataLog("   It's already materialized.\n");
                            return;
                        }
                        
                        createMaterialize(escapee, node);
                    });
            }
        }
        
        // Second pass: find CFG edge materialization points.
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            for (BasicBlock* successorBlock : block->successors()) {
                for (auto pair : materializedAtHead[successorBlock]) {
                    Node* allocation = pair.key;
                    
                    // We only care if it is materialized in the successor.
                    if (!pair.value)
                        continue;
                    
                    // We only care about sinking candidates.
                    if (!m_sinkCandidates.contains(allocation))
                        continue;
                    
                    // We only care if it isn't materialized in the predecessor.
                    if (materializedAtTail[block].get(allocation))
                        continue;
                    
                    // We need to decide if we put the materialization at the head of the successor,
                    // or the tail of the predecessor. It needs to be placed so that the allocation
                    // is never materialized before, and always materialized after.
                    
                    // Is it never materialized in any of successor's predecessors? I like to think
                    // of "successors' predecessors" and "predecessor's successors" as the "shadow",
                    // because of what it looks like when you draw it.
                    bool neverMaterializedInShadow = true;
                    for (BasicBlock* shadowBlock : successorBlock->predecessors) {
                        if (materializedAtTail[shadowBlock].get(allocation)) {
                            neverMaterializedInShadow = false;
                            break;
                        }
                    }
                    
                    if (neverMaterializedInShadow) {
                        createMaterialize(allocation, successorBlock->firstOriginNode());
                        continue;
                    }
                    
                    // Because we had broken critical edges, it must be the case that the
                    // predecessor's successors all materialize the object. This is because the
                    // previous case is guaranteed to catch the case where the successor only has
                    // one predecessor. When critical edges are broken, this is also the only case
                    // where the predecessor could have had multiple successors. Therefore we have
                    // already handled the case where the predecessor has multiple successors.
                    DFG_ASSERT(m_graph, block, block->numSuccessors() == 1);
                    
                    createMaterialize(allocation, block->terminal());
                }
            }
        }
    }
    
    void placeMaterializationPoints()
    {
        m_ssaCalculator.reset();
        
        // The "variables" are the object allocations that we are sinking. So, nodeToVariable maps
        // sink candidates (aka escapees) to the SSACalculator's notion of Variable, and indexToNode
        // maps in the opposite direction using the SSACalculator::Variable::index() as the key.
        HashMap<Node*, SSACalculator::Variable*> nodeToVariable;
        Vector<Node*> indexToNode;
        
        for (Node* node : m_sinkCandidates) {
            SSACalculator::Variable* variable = m_ssaCalculator.newVariable();
            nodeToVariable.add(node, variable);
            ASSERT(indexToNode.size() == variable->index());
            indexToNode.append(node);
        }
        
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            for (Node* node : *block) {
                if (SSACalculator::Variable* variable = nodeToVariable.get(node))
                    m_ssaCalculator.newDef(variable, block, node);
                
                for (Node* materialize : m_materializationSiteToMaterializations.get(node)) {
                    m_ssaCalculator.newDef(
                        nodeToVariable.get(m_materializationToEscapee.get(materialize)),
                        block, materialize);
                }
            }
        }
        
        m_ssaCalculator.computePhis(
            [&] (SSACalculator::Variable* variable, BasicBlock* block) -> Node* {
                Node* allocation = indexToNode[variable->index()];
                if (!m_combinedLiveness.liveAtHead[block].contains(allocation))
                    return nullptr;
                
                Node* phiNode = m_graph.addNode(allocation->prediction(), Phi, NodeOrigin());
                phiNode->mergeFlags(NodeResultJS);
                return phiNode;
            });
        
        // Place Phis in the right places. Replace all uses of any allocation with the appropriate
        // materialization. Create the appropriate Upsilon nodes.
        LocalOSRAvailabilityCalculator availabilityCalculator;
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            HashMap<Node*, Node*> mapping;
            
            for (Node* candidate : m_combinedLiveness.liveAtHead[block]) {
                SSACalculator::Variable* variable = nodeToVariable.get(candidate);
                if (!variable)
                    continue;
                
                SSACalculator::Def* def = m_ssaCalculator.reachingDefAtHead(block, variable);
                if (!def)
                    continue;
                
                mapping.set(indexToNode[variable->index()], def->value());
            }
            
            availabilityCalculator.beginBlock(block);
            for (SSACalculator::Def* phiDef : m_ssaCalculator.phisForBlock(block)) {
                m_insertionSet.insert(0, phiDef->value());
                
                Node* originalNode = indexToNode[phiDef->variable()->index()];
                insertOSRHintsForUpdate(
                    m_insertionSet, 0, NodeOrigin(), availabilityCalculator.m_availability,
                    originalNode, phiDef->value());

                mapping.set(originalNode, phiDef->value());
            }
            
            for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
                Node* node = block->at(nodeIndex);

                for (Node* materialize : m_materializationSiteToMaterializations.get(node)) {
                    Node* escapee = m_materializationToEscapee.get(materialize);
                    m_insertionSet.insert(nodeIndex, materialize);
                    insertOSRHintsForUpdate(
                        m_insertionSet, nodeIndex, node->origin,
                        availabilityCalculator.m_availability, escapee, materialize);
                    mapping.set(escapee, materialize);
                }
                    
                availabilityCalculator.executeNode(node);
                
                m_graph.doToChildren(
                    node,
                    [&] (Edge& edge) {
                        if (Node* materialize = mapping.get(edge.node()))
                            edge.setNode(materialize);
                    });
                
                // If you cause an escape, you shouldn't see the original escapee.
                if (validationEnabled()) {
                    handleNode(
                        node,
                        [&] () { },
                        [&] (Node* escapee) {
                            DFG_ASSERT(m_graph, node, !m_sinkCandidates.contains(escapee));
                        });
                }
            }
            
            NodeAndIndex terminal = block->findTerminal();
            size_t upsilonInsertionPoint = terminal.index;
            Node* upsilonWhere = terminal.node;
            NodeOrigin upsilonOrigin = upsilonWhere->origin;
            for (BasicBlock* successorBlock : block->successors()) {
                for (SSACalculator::Def* phiDef : m_ssaCalculator.phisForBlock(successorBlock)) {
                    Node* phiNode = phiDef->value();
                    SSACalculator::Variable* variable = phiDef->variable();
                    Node* allocation = indexToNode[variable->index()];
                    
                    Node* incoming = mapping.get(allocation);
                    DFG_ASSERT(m_graph, incoming, incoming != allocation);
                    
                    m_insertionSet.insertNode(
                        upsilonInsertionPoint, SpecNone, Upsilon, upsilonOrigin,
                        OpInfo(phiNode), incoming->defaultEdge());
                }
            }
            
            m_insertionSet.execute(block);
        }
        
        // At this point we have dummy materialization nodes along with edges to them. This means
        // that the part of the control flow graph that prefers to see actual object allocations
        // is completely fixed up, except for the materializations themselves.
    }
    
    void lowerNonReadingOperationsOnPhantomAllocations()
    {
        // Lower everything but reading operations on phantom allocations. We absolutely have to
        // lower all writes so as to reveal them to the SSA calculator. We cannot lower reads
        // because the whole point is that those go away completely.
        
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
                Node* node = block->at(nodeIndex);
                switch (node->op()) {
                case PutByOffset: {
                    Node* target = node->child2().node();
                    if (m_sinkCandidates.contains(target)) {
                        ASSERT(target->isPhantomObjectAllocation());
                        node->convertToPutByOffsetHint();
                    }
                    break;
                }

                case PutClosureVar: {
                    Node* target = node->child1().node();
                    if (m_sinkCandidates.contains(target)) {
                        ASSERT(target->isPhantomActivationAllocation());
                        node->convertToPutClosureVarHint();
                    }
                    break;
                }
                    
                case PutStructure: {
                    Node* target = node->child1().node();
                    if (m_sinkCandidates.contains(target)) {
                        ASSERT(target->isPhantomObjectAllocation());
                        Node* structure = m_insertionSet.insertConstant(
                            nodeIndex, node->origin, JSValue(node->transition()->next));
                        node->convertToPutStructureHint(structure);
                    }
                    break;
                }
                    
                case NewObject: {
                    if (m_sinkCandidates.contains(node)) {
                        Node* structure = m_insertionSet.insertConstant(
                            nodeIndex + 1, node->origin, JSValue(node->structure()));
                        m_insertionSet.insert(
                            nodeIndex + 1,
                            PromotedHeapLocation(StructurePLoc, node).createHint(
                                m_graph, node->origin, structure));
                        node->convertToPhantomNewObject();
                    }
                    break;
                }
                    
                case MaterializeNewObject: {
                    if (m_sinkCandidates.contains(node)) {
                        m_insertionSet.insert(
                            nodeIndex + 1,
                            PromotedHeapLocation(StructurePLoc, node).createHint(
                                m_graph, node->origin, m_graph.varArgChild(node, 0).node()));
                        for (unsigned i = 0; i < node->objectMaterializationData().m_properties.size(); ++i) {
                            unsigned identifierNumber =
                                node->objectMaterializationData().m_properties[i].m_identifierNumber;
                            m_insertionSet.insert(
                                nodeIndex + 1,
                                PromotedHeapLocation(
                                    NamedPropertyPLoc, node, identifierNumber).createHint(
                                    m_graph, node->origin,
                                    m_graph.varArgChild(node, i + 1).node()));
                        }
                        node->convertToPhantomNewObject();
                    }
                    break;
                }

                case NewFunction: {
                    if (m_sinkCandidates.contains(node)) {
                        Node* executable = m_insertionSet.insertConstant(
                            nodeIndex + 1, node->origin, node->cellOperand());
                        m_insertionSet.insert(
                            nodeIndex + 1,
                            PromotedHeapLocation(FunctionExecutablePLoc, node).createHint(
                                m_graph, node->origin, executable));
                        m_insertionSet.insert(
                            nodeIndex + 1,
                            PromotedHeapLocation(FunctionActivationPLoc, node).createHint(
                                m_graph, node->origin, node->child1().node()));
                        node->convertToPhantomNewFunction();
                    }
                    break;
                }

                case CreateActivation: {
                    if (m_sinkCandidates.contains(node)) {
                        m_insertionSet.insert(
                            nodeIndex + 1,
                            PromotedHeapLocation(ActivationScopePLoc, node).createHint(
                                m_graph, node->origin, node->child1().node()));
                        Node* symbolTableNode = m_insertionSet.insertConstant(
                            nodeIndex + 1, node->origin, node->cellOperand());
                        m_insertionSet.insert(
                            nodeIndex + 1,
                            PromotedHeapLocation(ActivationSymbolTablePLoc, node).createHint(
                                m_graph, node->origin, symbolTableNode));

                        {
                            SymbolTable* symbolTable = node->castOperand<SymbolTable*>();
                            Node* undefined = m_insertionSet.insertConstant(
                                nodeIndex + 1, node->origin, jsUndefined());
                            ConcurrentJITLocker locker(symbolTable->m_lock);
                            for (auto iter = symbolTable->begin(locker), end = symbolTable->end(locker); iter != end; ++iter) {
                                m_insertionSet.insert(
                                    nodeIndex + 1,
                                    PromotedHeapLocation(
                                        ClosureVarPLoc, node, iter->value.scopeOffset().offset()).createHint(
                                        m_graph, node->origin, undefined));
                            }
                        }

                        node->convertToPhantomCreateActivation();
                    }
                    break;
                }

                case MaterializeCreateActivation: {
                    if (m_sinkCandidates.contains(node)) {
                        m_insertionSet.insert(
                            nodeIndex + 1,
                            PromotedHeapLocation(ActivationScopePLoc, node).createHint(
                                m_graph, node->origin, m_graph.varArgChild(node, 0).node()));
                        Node* symbolTableNode = m_insertionSet.insertConstant(
                            nodeIndex + 1, node->origin, node->cellOperand());
                        m_insertionSet.insert(
                            nodeIndex + 1,
                            PromotedHeapLocation(ActivationSymbolTablePLoc, node).createHint(
                                m_graph, node->origin, symbolTableNode));
                        ObjectMaterializationData& data = node->objectMaterializationData();
                        for (unsigned i = 0; i < data.m_properties.size(); ++i) {
                            unsigned identifierNumber = data.m_properties[i].m_identifierNumber;
                            m_insertionSet.insert(
                                nodeIndex + 1,
                                PromotedHeapLocation(
                                    ClosureVarPLoc, node, identifierNumber).createHint(
                                    m_graph, node->origin,
                                    m_graph.varArgChild(node, i + 1).node()));
                        }
                        node->convertToPhantomCreateActivation();
                    }
                    break;
                }

                case StoreBarrier: {
                    DFG_CRASH(m_graph, node, "Unexpected store barrier during sinking.");
                    break;
                }
                    
                default:
                    break;
                }
                
                m_graph.doToChildren(
                    node,
                    [&] (Edge& edge) {
                        if (m_sinkCandidates.contains(edge.node()))
                            edge.setUseKind(KnownCellUse);
                    });
            }
            m_insertionSet.execute(block);
        }
    }
    
    void promoteSunkenFields()
    {
        // Collect the set of heap locations that we will be operating over.
        HashSet<PromotedHeapLocation> locations;
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            for (Node* node : *block) {
                promoteHeapAccess(
                    node,
                    [&] (PromotedHeapLocation location, Edge) {
                        if (m_sinkCandidates.contains(location.base()))
                            locations.add(location);
                    },
                    [&] (PromotedHeapLocation location) {
                        if (m_sinkCandidates.contains(location.base()))
                            locations.add(location);
                    });
            }
        }
        
        // Figure out which locations belong to which allocations.
        m_locationsForAllocation.clear();
        for (PromotedHeapLocation location : locations) {
            auto result = m_locationsForAllocation.add(location.base(), Vector<PromotedHeapLocation>());
            ASSERT(!result.iterator->value.contains(location));
            result.iterator->value.append(location);
        }
        
        // For each sunken thingy, make sure we create Bottom values for all of its fields.
        // Note that this has the hilarious slight inefficiency of creating redundant hints for
        // things that were previously materializations. This should only impact compile times and
        // not code quality, and it's necessary for soundness without some data structure hackage.
        // For example, a MaterializeNewObject that we choose to sink may have new fields added to
        // it conditionally. That would necessitate Bottoms.
        Node* bottom = nullptr;
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            if (block == m_graph.block(0))
                bottom = m_insertionSet.insertConstant(0, NodeOrigin(), jsUndefined());
            
            for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
                Node* node = block->at(nodeIndex);
                for (PromotedHeapLocation location : m_locationsForAllocation.get(node)) {
                    m_insertionSet.insert(
                        nodeIndex + 1, location.createHint(m_graph, node->origin, bottom));
                }
            }
            m_insertionSet.execute(block);
        }

        m_ssaCalculator.reset();

        // Collect the set of "variables" that we will be sinking.
        m_locationToVariable.clear();
        m_indexToLocation.clear();
        for (PromotedHeapLocation location : locations) {
            SSACalculator::Variable* variable = m_ssaCalculator.newVariable();
            m_locationToVariable.add(location, variable);
            ASSERT(m_indexToLocation.size() == variable->index());
            m_indexToLocation.append(location);
        }
        
        // Create Defs from the existing hints.
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            for (Node* node : *block) {
                promoteHeapAccess(
                    node,
                    [&] (PromotedHeapLocation location, Edge value) {
                        if (!m_sinkCandidates.contains(location.base()))
                            return;
                        SSACalculator::Variable* variable = m_locationToVariable.get(location);
                        m_ssaCalculator.newDef(variable, block, value.node());
                    },
                    [&] (PromotedHeapLocation) { });
            }
        }
        
        // OMG run the SSA calculator to create Phis!
        m_ssaCalculator.computePhis(
            [&] (SSACalculator::Variable* variable, BasicBlock* block) -> Node* {
                PromotedHeapLocation location = m_indexToLocation[variable->index()];
                if (!m_combinedLiveness.liveAtHead[block].contains(location.base()))
                    return nullptr;
                
                Node* phiNode = m_graph.addNode(SpecHeapTop, Phi, NodeOrigin());
                phiNode->mergeFlags(NodeResultJS);
                return phiNode;
            });
        
        // Place Phis in the right places, replace all uses of any load with the appropriate
        // value, and create the appropriate Upsilon nodes.
        m_graph.clearReplacements();
        for (BasicBlock* block : m_graph.blocksInPreOrder()) {
            // This mapping table is intended to be lazy. If something is omitted from the table,
            // it means that there haven't been any local stores to that promoted heap location.
            m_localMapping.clear();
            
            // Insert the Phi functions that we had previously created.
            for (SSACalculator::Def* phiDef : m_ssaCalculator.phisForBlock(block)) {
                PromotedHeapLocation location = m_indexToLocation[phiDef->variable()->index()];
                
                m_insertionSet.insert(
                    0, phiDef->value());
                m_insertionSet.insert(
                    0, location.createHint(m_graph, NodeOrigin(), phiDef->value()));
                m_localMapping.add(location, phiDef->value());
            }
            
            if (verbose)
                dataLog("Local mapping at ", pointerDump(block), ": ", mapDump(m_localMapping), "\n");
            
            // Process the block and replace all uses of loads with the promoted value.
            for (Node* node : *block) {
                m_graph.performSubstitution(node);
                
                if (Node* escapee = m_materializationToEscapee.get(node))
                    populateMaterialize(block, node, escapee);
                
                promoteHeapAccess(
                    node,
                    [&] (PromotedHeapLocation location, Edge value) {
                        if (m_sinkCandidates.contains(location.base()))
                            m_localMapping.set(location, value.node());
                    },
                    [&] (PromotedHeapLocation location) {
                        if (m_sinkCandidates.contains(location.base())) {
                            switch (node->op()) {
                            case CheckStructure:
                                node->convertToCheckStructureImmediate(resolve(block, location));
                                break;

                            default:
                                node->replaceWith(resolve(block, location));
                                break;
                            }
                        }
                    });
            }
            
            // Gotta drop some Upsilons.
            NodeAndIndex terminal = block->findTerminal();
            size_t upsilonInsertionPoint = terminal.index;
            NodeOrigin upsilonOrigin = terminal.node->origin;
            for (BasicBlock* successorBlock : block->successors()) {
                for (SSACalculator::Def* phiDef : m_ssaCalculator.phisForBlock(successorBlock)) {
                    Node* phiNode = phiDef->value();
                    SSACalculator::Variable* variable = phiDef->variable();
                    PromotedHeapLocation location = m_indexToLocation[variable->index()];
                    Node* incoming = resolve(block, location);
                    
                    m_insertionSet.insertNode(
                        upsilonInsertionPoint, SpecNone, Upsilon, upsilonOrigin,
                        OpInfo(phiNode), incoming->defaultEdge());
                }
            }
            
            m_insertionSet.execute(block);
        }
    }
    
    Node* resolve(BasicBlock* block, PromotedHeapLocation location)
    {
        if (Node* result = m_localMapping.get(location))
            return result;
        
        // This implies that there is no local mapping. Find a non-local mapping.
        SSACalculator::Def* def = m_ssaCalculator.nonLocalReachingDef(
            block, m_locationToVariable.get(location));
        ASSERT(def);
        ASSERT(def->value());
        m_localMapping.add(location, def->value());
        return def->value();
    }

    template<typename SinkCandidateFunctor, typename EscapeFunctor>
    void handleNode(
        Node* node,
        const SinkCandidateFunctor& sinkCandidate,
        const EscapeFunctor& escape)
    {
        switch (node->op()) {
        case NewObject:
        case MaterializeNewObject:
        case MaterializeCreateActivation:
            sinkCandidate();
            m_graph.doToChildren(
                node,
                [&] (Edge edge) {
                    escape(edge.node());
                });
            break;

        case NewFunction:
            if (!node->castOperand<FunctionExecutable*>()->singletonFunction()->isStillValid())
                sinkCandidate();
            escape(node->child1().node());
            break;

        case CreateActivation:
            if (!node->castOperand<SymbolTable*>()->singletonScope()->isStillValid())
                sinkCandidate();
            escape(node->child1().node());
            break;

        case Check:
            m_graph.doToChildren(
                node,
                [&] (Edge edge) {
                    if (edge.willNotHaveCheck())
                        return;
                    
                    if (alreadyChecked(edge.useKind(), SpecObject))
                        return;
                    
                    escape(edge.node());
                });
            break;

        case MovHint:
        case PutHint:
            break;

        case PutStructure:
        case CheckStructure:
        case GetByOffset:
        case MultiGetByOffset:
        case GetGetterSetterByOffset: {
            Node* target = node->child1().node();
            if (!target->isObjectAllocation())
                escape(target);
            break;
        }
            
        case PutByOffset: {
            Node* target = node->child2().node();
            if (!target->isObjectAllocation()) {
                escape(target);
                escape(node->child1().node());
            }
            escape(node->child3().node());
            break;
        }

        case GetClosureVar: {
            Node* target = node->child1().node();
            if (!target->isActivationAllocation())
                escape(target);
            break;
        }

        case PutClosureVar: {
            Node* target = node->child1().node();
            if (!target->isActivationAllocation())
                escape(target);
            escape(node->child2().node());
            break;
        }

        case GetScope: {
            Node* target = node->child1().node();
            if (!target->isFunctionAllocation())
                escape(target);
            break;
        }

        case GetExecutable: {
            Node* target = node->child1().node();
            if (!target->isFunctionAllocation())
                escape(target);
            break;
        }

        case SkipScope: {
            Node* target = node->child1().node();
            if (!target->isActivationAllocation())
                escape(target);
            break;
        }
            
        case MultiPutByOffset:
            // FIXME: In the future we should be able to handle this. It's just a matter of
            // building the appropriate *Hint variant of this instruction, along with a
            // PhantomStructureSelect node - since this transforms the Structure in a conditional
            // way.
            // https://bugs.webkit.org/show_bug.cgi?id=136924
            escape(node->child1().node());
            escape(node->child2().node());
            break;

        default:
            m_graph.doToChildren(
                node,
                [&] (Edge edge) {
                    escape(edge.node());
                });
            break;
        }
    }
    
    Node* createMaterialize(Node* escapee, Node* where)
    {
        Node* result = nullptr;
        
        switch (escapee->op()) {
        case NewObject:
        case MaterializeNewObject: {
            ObjectMaterializationData* data = m_graph.m_objectMaterializationData.add();
            
            result = m_graph.addNode(
                escapee->prediction(), Node::VarArg, MaterializeNewObject,
                NodeOrigin(
                    escapee->origin.semantic,
                    where->origin.forExit),
                OpInfo(data), OpInfo(), 0, 0);
            break;
        }

        case NewFunction:
            result = m_graph.addNode(
                escapee->prediction(), NewFunction,
                NodeOrigin(
                    escapee->origin.semantic,
                    where->origin.forExit),
                OpInfo(escapee->cellOperand()),
                escapee->child1());
            break;

        case CreateActivation:
        case MaterializeCreateActivation: {
            ObjectMaterializationData* data = m_graph.m_objectMaterializationData.add();
            FrozenValue* symbolTable = escapee->cellOperand();
            result = m_graph.addNode(
                escapee->prediction(), Node::VarArg, MaterializeCreateActivation,
                NodeOrigin(
                    escapee->origin.semantic,
                    where->origin.forExit),
                OpInfo(data), OpInfo(symbolTable), 0, 0);
            break;
        }

        default:
            DFG_CRASH(m_graph, escapee, "Bad escapee op");
            break;
        }
        
        if (verbose)
            dataLog("Creating materialization point at ", where, " for ", escapee, ": ", result, "\n");
        
        m_materializationToEscapee.add(result, escapee);
        m_materializationSiteToMaterializations.add(
            where, Vector<Node*>()).iterator->value.append(result);
        
        return result;
    }
    
    void populateMaterialize(BasicBlock* block, Node* node, Node* escapee)
    {
        switch (node->op()) {
        case MaterializeNewObject: {
            ObjectMaterializationData& data = node->objectMaterializationData();
            unsigned firstChild = m_graph.m_varArgChildren.size();
            
            Vector<PromotedHeapLocation> locations = m_locationsForAllocation.get(escapee);
            
            PromotedHeapLocation structure(StructurePLoc, escapee);
            ASSERT(locations.contains(structure));
            
            m_graph.m_varArgChildren.append(Edge(resolve(block, structure), KnownCellUse));
            
            for (unsigned i = 0; i < locations.size(); ++i) {
                switch (locations[i].kind()) {
                case StructurePLoc: {
                    ASSERT(locations[i] == structure);
                    break;
                }
                    
                case NamedPropertyPLoc: {
                    Node* value = resolve(block, locations[i]);
                    if (value->op() == BottomValue) {
                        // We can skip Bottoms entirely.
                        break;
                    }
                    
                    data.m_properties.append(PhantomPropertyValue(locations[i].info()));
                    m_graph.m_varArgChildren.append(value);
                    break;
                }
                    
                default:
                    DFG_CRASH(m_graph, node, "Bad location kind");
                }
            }
            
            node->children = AdjacencyList(
                AdjacencyList::Variable,
                firstChild, m_graph.m_varArgChildren.size() - firstChild);
            break;
        }

        case MaterializeCreateActivation: {
            ObjectMaterializationData& data = node->objectMaterializationData();

            unsigned firstChild = m_graph.m_varArgChildren.size();

            Vector<PromotedHeapLocation> locations = m_locationsForAllocation.get(escapee);

            PromotedHeapLocation scope(ActivationScopePLoc, escapee);
            PromotedHeapLocation symbolTable(ActivationSymbolTablePLoc, escapee);
            ASSERT(locations.contains(scope));

            m_graph.m_varArgChildren.append(Edge(resolve(block, scope), KnownCellUse));

            for (unsigned i = 0; i < locations.size(); ++i) {
                switch (locations[i].kind()) {
                case ActivationScopePLoc: {
                    ASSERT(locations[i] == scope);
                    break;
                }

                case ActivationSymbolTablePLoc: {
                    ASSERT(locations[i] == symbolTable);
                    break;
                }

                case ClosureVarPLoc: {
                    Node* value = resolve(block, locations[i]);
                    if (value->op() == BottomValue)
                        break;

                    data.m_properties.append(PhantomPropertyValue(locations[i].info()));
                    m_graph.m_varArgChildren.append(value);
                    break;
                }

                default:
                    DFG_CRASH(m_graph, node, "Bad location kind");
                }
            }

            node->children = AdjacencyList(
                AdjacencyList::Variable,
                firstChild, m_graph.m_varArgChildren.size() - firstChild);
            break;
        }

        case NewFunction: {
            if (!ASSERT_DISABLED) {
                Vector<PromotedHeapLocation> locations = m_locationsForAllocation.get(escapee);

                ASSERT(locations.size() == 2);

                PromotedHeapLocation executable(FunctionExecutablePLoc, escapee);
                ASSERT(locations.contains(executable));

                PromotedHeapLocation activation(FunctionActivationPLoc, escapee);
                ASSERT(locations.contains(activation));

                for (unsigned i = 0; i < locations.size(); ++i) {
                    switch (locations[i].kind()) {
                    case FunctionExecutablePLoc: {
                        ASSERT(locations[i] == executable);
                        break;
                    }

                    case FunctionActivationPLoc: {
                        ASSERT(locations[i] == activation);
                        break;
                    }

                    default:
                        DFG_CRASH(m_graph, node, "Bad location kind");
                    }
                }
            }

            break;
        }

        default:
            DFG_CRASH(m_graph, node, "Bad materialize op");
            break;
        }
    }
    
    CombinedLiveness m_combinedLiveness;
    SSACalculator m_ssaCalculator;
    HashSet<Node*> m_sinkCandidates;
    HashMap<Node*, Node*> m_materializationToEscapee;
    HashMap<Node*, Vector<Node*>> m_materializationSiteToMaterializations;
    HashMap<Node*, Vector<PromotedHeapLocation>> m_locationsForAllocation;
    HashMap<PromotedHeapLocation, SSACalculator::Variable*> m_locationToVariable;
    Vector<PromotedHeapLocation> m_indexToLocation;
    HashMap<PromotedHeapLocation, Node*> m_localMapping;
    InsertionSet m_insertionSet;
};
    
bool performObjectAllocationSinking(Graph& graph)
{
    SamplingRegion samplingRegion("DFG Object Allocation Sinking Phase");
    return runPhase<ObjectAllocationSinkingPhase>(graph);
}

} } // namespace JSC::DFG

#endif // ENABLE(DFG_JIT)