DFGInPlaceAbstractState.cpp   [plain text]


/*
 * Copyright (C) 2013-2018 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 "DFGInPlaceAbstractState.h"

#if ENABLE(DFG_JIT)

#include "CodeBlock.h"
#include "DFGBasicBlock.h"
#include "GetByIdStatus.h"
#include "JSCInlines.h"
#include "PutByIdStatus.h"
#include "StringObject.h"
#include "SuperSampler.h"

namespace JSC { namespace DFG {

namespace DFGInPlaceAbstractStateInternal {
static const bool verbose = false;
}

InPlaceAbstractState::InPlaceAbstractState(Graph& graph)
    : m_graph(graph)
    , m_abstractValues(*graph.m_abstractValuesCache)
    , m_variables(m_graph.m_codeBlock->numParameters(), graph.m_localVars)
    , m_block(0)
{
}

InPlaceAbstractState::~InPlaceAbstractState() { }

void InPlaceAbstractState::beginBasicBlock(BasicBlock* basicBlock)
{
    ASSERT(!m_block);
    
    ASSERT(basicBlock->variablesAtHead.numberOfLocals() == basicBlock->valuesAtHead.numberOfLocals());
    ASSERT(basicBlock->variablesAtTail.numberOfLocals() == basicBlock->valuesAtTail.numberOfLocals());
    ASSERT(basicBlock->variablesAtHead.numberOfLocals() == basicBlock->variablesAtTail.numberOfLocals());

    m_abstractValues.resize();

    AbstractValueClobberEpoch epoch = AbstractValueClobberEpoch::first(basicBlock->cfaStructureClobberStateAtHead);
    m_epochAtHead = epoch;
    m_effectEpoch = epoch;

    m_block = basicBlock;

    m_activeVariables.clearRange(0, std::min(m_variables.size(), m_activeVariables.size()));
    if (m_variables.size() > m_activeVariables.size())
        m_activeVariables.resize(m_variables.size());
    
    if (m_graph.m_form == SSA) {
        for (NodeAbstractValuePair& entry : basicBlock->ssa->valuesAtHead) {
            if (entry.node.isStillValid()) {
                AbstractValue& value = m_abstractValues.at(entry.node);
                value = entry.value;
                value.m_effectEpoch = epoch;
            }
        }
    }
    basicBlock->cfaShouldRevisit = false;
    basicBlock->cfaHasVisited = true;
    m_isValid = true;
    m_foundConstants = false;
    m_branchDirection = InvalidBranchDirection;
    m_structureClobberState = basicBlock->cfaStructureClobberStateAtHead;
}

static void setLiveValues(Vector<NodeAbstractValuePair>& values, const Vector<NodeFlowProjection>& live)
{
    values.shrink(0);
    values.reserveCapacity(live.size());
    for (NodeFlowProjection node : live)
        values.uncheckedAppend(NodeAbstractValuePair { node, AbstractValue() });
}

Operands<AbstractValue>& InPlaceAbstractState::variablesForDebugging()
{
    activateAllVariables();
    return m_variables;
}

void InPlaceAbstractState::activateAllVariables()
{
    for (size_t i = m_variables.size(); i--;)
        activateVariableIfNecessary(i);
}

void InPlaceAbstractState::initialize()
{
    for (BasicBlock* entrypoint : m_graph.m_roots) {
        entrypoint->cfaShouldRevisit = true;
        entrypoint->cfaHasVisited = false;
        entrypoint->cfaFoundConstants = false;
        entrypoint->cfaStructureClobberStateAtHead = StructuresAreWatched;
        entrypoint->cfaStructureClobberStateAtTail = StructuresAreWatched;

        if (m_graph.m_form == SSA)  {
            for (size_t i = 0; i < entrypoint->valuesAtHead.numberOfArguments(); ++i) {
                entrypoint->valuesAtHead.argument(i).clear();
                entrypoint->valuesAtTail.argument(i).clear();
            }
        } else {
            const ArgumentsVector& arguments = m_graph.m_rootToArguments.find(entrypoint)->value;
            for (size_t i = 0; i < entrypoint->valuesAtHead.numberOfArguments(); ++i) {
                entrypoint->valuesAtTail.argument(i).clear();

                FlushFormat format;
                Node* node = arguments[i];
                if (!node)
                    format = FlushedJSValue;
                else {
                    ASSERT(node->op() == SetArgument);
                    format = node->variableAccessData()->flushFormat();
                }

                switch (format) {
                case FlushedInt32:
                    entrypoint->valuesAtHead.argument(i).setNonCellType(SpecInt32Only);
                    break;
                case FlushedBoolean:
                    entrypoint->valuesAtHead.argument(i).setNonCellType(SpecBoolean);
                    break;
                case FlushedCell:
                    entrypoint->valuesAtHead.argument(i).setType(m_graph, SpecCellCheck);
                    break;
                case FlushedJSValue:
                    entrypoint->valuesAtHead.argument(i).makeBytecodeTop();
                    break;
                default:
                    DFG_CRASH(m_graph, nullptr, "Bad flush format for argument");
                    break;
                }
            }
        }

        for (size_t i = 0; i < entrypoint->valuesAtHead.numberOfLocals(); ++i) {
            entrypoint->valuesAtHead.local(i).clear();
            entrypoint->valuesAtTail.local(i).clear();
        }
    }

    for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
        if (m_graph.isRoot(block)) {
            // We bootstrapped the CFG roots above.
            continue;
        }

        ASSERT(block->isReachable);
        block->cfaShouldRevisit = false;
        block->cfaHasVisited = false;
        block->cfaFoundConstants = false;
        block->cfaStructureClobberStateAtHead = StructuresAreWatched;
        block->cfaStructureClobberStateAtTail = StructuresAreWatched;
        for (size_t i = 0; i < block->valuesAtHead.numberOfArguments(); ++i) {
            block->valuesAtHead.argument(i).clear();
            block->valuesAtTail.argument(i).clear();
        }
        for (size_t i = 0; i < block->valuesAtHead.numberOfLocals(); ++i) {
            block->valuesAtHead.local(i).clear();
            block->valuesAtTail.local(i).clear();
        }
    }

    if (m_graph.m_form == SSA) {
        for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex) {
            BasicBlock* block = m_graph.block(blockIndex);
            if (!block)
                continue;
            setLiveValues(block->ssa->valuesAtHead, block->ssa->liveAtHead);
            setLiveValues(block->ssa->valuesAtTail, block->ssa->liveAtTail);
        }
    }
}

bool InPlaceAbstractState::endBasicBlock()
{
    ASSERT(m_block);
    
    BasicBlock* block = m_block; // Save the block for successor merging.
    
    block->cfaFoundConstants = m_foundConstants;
    block->cfaDidFinish = m_isValid;
    block->cfaBranchDirection = m_branchDirection;
    
    if (!m_isValid) {
        reset();
        return false;
    }

    AbstractValueClobberEpoch epochAtHead = m_epochAtHead;
    AbstractValueClobberEpoch currentEpoch = m_effectEpoch;

    block->cfaStructureClobberStateAtTail = m_structureClobberState;
    
    switch (m_graph.m_form) {
    case ThreadedCPS: {
        ASSERT(block->variablesAtTail.size() == block->valuesAtTail.size());
        ASSERT(block->variablesAtTail.size() == m_variables.size());
        for (size_t index = m_variables.size(); index--;) {
            Node* node = block->variablesAtTail[index];
            if (!node)
                continue;
            AbstractValue& destination = block->valuesAtTail[index];
            
            if (!m_activeVariables[index]) {
                destination = block->valuesAtHead[index];
                destination.fastForwardFromTo(epochAtHead, currentEpoch);
                continue;
            }
            
            switch (node->op()) {
            case Phi:
            case SetArgument:
            case PhantomLocal:
            case Flush: {
                // The block transfers the value from head to tail.
                destination = variableAt(index);
                break;
            }
                
            case GetLocal: {
                // The block refines the value with additional speculations.
                destination = forNode(node);

                // We need to make sure that we don't broaden the type beyond what the flush
                // format says it will be. The value may claim to have changed abstract state
                // but it's type cannot change without a store. For example:
                //
                // Block #1:
                // 0: GetLocal(loc42, FlushFormatInt32)
                // 1: PutStructure(Check: Cell: @0, ArrayStructure)
                // ...
                // 2: Branch(T: #1, F: #2)
                //
                // In this case the AbstractState of @0 will say it's an SpecArray but the only
                // reason that would have happened is because we would have exited the cell check.

                FlushFormat flushFormat = node->variableAccessData()->flushFormat();
                destination.filter(typeFilterFor(flushFormat));
                break;
            }
            case SetLocal: {
                // The block sets the variable, and potentially refines it, both
                // before and after setting it.
                destination = forNode(node->child1());
                break;
            }
                
            default:
                RELEASE_ASSERT_NOT_REACHED();
                break;
            }
        }
        break;
    }

    case SSA: {
        for (size_t i = 0; i < block->valuesAtTail.size(); ++i) {
            AbstractValue& destination = block->valuesAtTail[i];

            if (!m_activeVariables[i]) {
                destination = block->valuesAtHead[i];
                destination.fastForwardFromTo(epochAtHead, currentEpoch);
                continue;
            }
            
            block->valuesAtTail[i] = variableAt(i);
        }

        for (NodeAbstractValuePair& valueAtTail : block->ssa->valuesAtTail)
            valueAtTail.value = forNode(valueAtTail.node);
        break;
    }

    default:
        RELEASE_ASSERT_NOT_REACHED();
    }

    reset();
    
    return mergeToSuccessors(block);
}

void InPlaceAbstractState::reset()
{
    m_block = 0;
    m_isValid = false;
    m_branchDirection = InvalidBranchDirection;
    m_structureClobberState = StructuresAreWatched;
}

void InPlaceAbstractState::activateVariable(size_t variableIndex)
{
    AbstractValue& value = m_variables[variableIndex];
    value = m_block->valuesAtHead[variableIndex];
    value.m_effectEpoch = m_epochAtHead;
    m_activeVariables[variableIndex] = true;
}

bool InPlaceAbstractState::merge(BasicBlock* from, BasicBlock* to)
{
    if (DFGInPlaceAbstractStateInternal::verbose)
        dataLog("   Merging from ", pointerDump(from), " to ", pointerDump(to), "\n");
    ASSERT(from->variablesAtTail.numberOfArguments() == to->variablesAtHead.numberOfArguments());
    ASSERT(from->variablesAtTail.numberOfLocals() == to->variablesAtHead.numberOfLocals());
    
    bool changed = false;
    
    changed |= checkAndSet(
        to->cfaStructureClobberStateAtHead,
        DFG::merge(from->cfaStructureClobberStateAtTail, to->cfaStructureClobberStateAtHead));
    
    switch (m_graph.m_form) {
    case ThreadedCPS: {
        for (size_t argument = 0; argument < from->variablesAtTail.numberOfArguments(); ++argument) {
            AbstractValue& destination = to->valuesAtHead.argument(argument);
            changed |= mergeVariableBetweenBlocks(destination, from->valuesAtTail.argument(argument), to->variablesAtHead.argument(argument), from->variablesAtTail.argument(argument));
        }
        
        for (size_t local = 0; local < from->variablesAtTail.numberOfLocals(); ++local) {
            AbstractValue& destination = to->valuesAtHead.local(local);
            changed |= mergeVariableBetweenBlocks(destination, from->valuesAtTail.local(local), to->variablesAtHead.local(local), from->variablesAtTail.local(local));
        }
        break;
    }
        
    case SSA: {
        for (size_t i = from->valuesAtTail.size(); i--;)
            changed |= to->valuesAtHead[i].merge(from->valuesAtTail[i]);

        for (NodeAbstractValuePair& entry : to->ssa->valuesAtHead) {
            NodeFlowProjection node = entry.node;
            if (DFGInPlaceAbstractStateInternal::verbose)
                dataLog("      Merging for ", node, ": from ", forNode(node), " to ", entry.value, "\n");
#ifndef NDEBUG
            unsigned valueCountInFromBlock = 0;
            for (NodeAbstractValuePair& fromBlockValueAtTail : from->ssa->valuesAtTail) {
                if (fromBlockValueAtTail.node == node) {
                    ASSERT(fromBlockValueAtTail.value == forNode(node));
                    ++valueCountInFromBlock;
                }
            }
            ASSERT(valueCountInFromBlock == 1);
#endif

            changed |= entry.value.merge(forNode(node));

            if (DFGInPlaceAbstractStateInternal::verbose)
                dataLog("         Result: ", entry.value, "\n");
        }
        break;
    }
        
    default:
        RELEASE_ASSERT_NOT_REACHED();
        break;
    }

    if (!to->cfaHasVisited)
        changed = true;
    
    if (DFGInPlaceAbstractStateInternal::verbose)
        dataLog("      Will revisit: ", changed, "\n");
    to->cfaShouldRevisit |= changed;
    
    return changed;
}

inline bool InPlaceAbstractState::mergeToSuccessors(BasicBlock* basicBlock)
{
    Node* terminal = basicBlock->terminal();
    
    ASSERT(terminal->isTerminal());
    
    switch (terminal->op()) {
    case Jump: {
        ASSERT(basicBlock->cfaBranchDirection == InvalidBranchDirection);
        return merge(basicBlock, terminal->targetBlock());
    }
        
    case Branch: {
        ASSERT(basicBlock->cfaBranchDirection != InvalidBranchDirection);
        bool changed = false;
        if (basicBlock->cfaBranchDirection != TakeFalse)
            changed |= merge(basicBlock, terminal->branchData()->taken.block);
        if (basicBlock->cfaBranchDirection != TakeTrue)
            changed |= merge(basicBlock, terminal->branchData()->notTaken.block);
        return changed;
    }
        
    case Switch: {
        // FIXME: It would be cool to be sparse conditional for Switch's. Currently
        // we're not. However I somehow doubt that this will ever be a big deal.
        ASSERT(basicBlock->cfaBranchDirection == InvalidBranchDirection);
        SwitchData* data = terminal->switchData();
        bool changed = merge(basicBlock, data->fallThrough.block);
        for (unsigned i = data->cases.size(); i--;)
            changed |= merge(basicBlock, data->cases[i].target.block);
        return changed;
    }
    
    case EntrySwitch: {
        EntrySwitchData* data = terminal->entrySwitchData();
        bool changed = false;
        for (unsigned i = data->cases.size(); i--;)
            changed |= merge(basicBlock, data->cases[i]);
        return changed;
    }

    case Return:
    case TailCall:
    case DirectTailCall:
    case TailCallVarargs:
    case TailCallForwardVarargs:
    case Unreachable:
    case Throw:
    case ThrowStaticError:
        ASSERT(basicBlock->cfaBranchDirection == InvalidBranchDirection);
        return false;

    default:
        RELEASE_ASSERT_NOT_REACHED();
        return false;
    }
}

inline bool InPlaceAbstractState::mergeVariableBetweenBlocks(AbstractValue& destination, AbstractValue& source, Node* destinationNode, Node* sourceNode)
{
    if (!destinationNode)
        return false;
    
    ASSERT_UNUSED(sourceNode, sourceNode);
    
    // FIXME: We could do some sparse conditional propagation here!
    
    return destination.merge(source);
}

} } // namespace JSC::DFG

#endif // ENABLE(DFG_JIT)