blob: d3a239eed57a6881fa2005a34050fcdefbece8b4 [file] [log] [blame]
/*
* Copyright (C) 2012 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 "DFGStructureCheckHoistingPhase.h"
#if ENABLE(DFG_JIT)
#include "DFGBasicBlock.h"
#include "DFGGraph.h"
#include "DFGInsertionSet.h"
#include "DFGPhase.h"
#include "DFGVariableAccessDataDump.h"
#include <wtf/HashMap.h>
namespace JSC { namespace DFG {
enum CheckBallot { VoteOther, VoteStructureCheck };
class StructureCheckHoistingPhase : public Phase {
public:
StructureCheckHoistingPhase(Graph& graph)
: Phase(graph, "structure check hoisting")
{
}
bool run()
{
for (unsigned i = m_graph.m_variableAccessData.size(); i--;) {
VariableAccessData* variable = &m_graph.m_variableAccessData[i];
if (!variable->isRoot())
continue;
variable->clearVotes();
}
// Identify the set of variables that are always subject to the same structure
// checks. For now, only consider monomorphic structure checks (one structure).
for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) {
BasicBlock* block = m_graph.m_blocks[blockIndex].get();
if (!block)
continue;
for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) {
NodeIndex nodeIndex = block->at(indexInBlock);
Node& node = m_graph[nodeIndex];
if (!node.shouldGenerate())
continue;
switch (node.op()) {
case CheckStructure:
case StructureTransitionWatchpoint: {
Node& child = m_graph[node.child1()];
if (child.op() != GetLocal)
break;
VariableAccessData* variable = child.variableAccessData();
variable->vote(VoteStructureCheck);
if (variable->isCaptured() || variable->structureCheckHoistingFailed())
break;
if (!isCellSpeculation(variable->prediction()))
break;
noticeStructureCheck(variable, node.structureSet());
break;
}
case ForwardCheckStructure:
case ForwardStructureTransitionWatchpoint:
// We currently rely on the fact that we're the only ones who would
// insert this node.
ASSERT_NOT_REACHED();
break;
case GetByOffset:
case PutByOffset:
case PutStructure:
case AllocatePropertyStorage:
case ReallocatePropertyStorage:
case GetButterfly:
case GetByVal:
case PutByVal:
case PutByValAlias:
case GetArrayLength:
case CheckArray:
case GetIndexedPropertyStorage:
case Phantom:
// Don't count these uses.
break;
case ArrayifyToStructure:
case Arrayify:
if (node.arrayMode().conversion() == Array::RageConvert) {
// Rage conversion changes structures. We should avoid tying to do
// any kind of hoisting when rage conversion is in play.
Node& child = m_graph[node.child1()];
if (child.op() != GetLocal)
break;
VariableAccessData* variable = child.variableAccessData();
variable->vote(VoteOther);
if (variable->isCaptured() || variable->structureCheckHoistingFailed())
break;
if (!isCellSpeculation(variable->prediction()))
break;
noticeStructureCheck(variable, 0);
}
break;
case SetLocal: {
// Find all uses of the source of the SetLocal. If any of them are a
// kind of CheckStructure, then we should notice them to ensure that
// we're not hoisting a check that would contravene checks that are
// already being performed.
VariableAccessData* variable = node.variableAccessData();
if (variable->isCaptured() || variable->structureCheckHoistingFailed())
break;
if (!isCellSpeculation(variable->prediction()))
break;
NodeIndex source = node.child1().index();
for (unsigned subIndexInBlock = 0; subIndexInBlock < block->size(); ++subIndexInBlock) {
NodeIndex subNodeIndex = block->at(subIndexInBlock);
Node& subNode = m_graph[subNodeIndex];
if (!subNode.shouldGenerate())
continue;
switch (subNode.op()) {
case CheckStructure: {
if (subNode.child1().index() != source)
break;
noticeStructureCheck(variable, subNode.structureSet());
break;
}
case StructureTransitionWatchpoint: {
if (subNode.child1().index() != source)
break;
noticeStructureCheck(variable, subNode.structure());
break;
}
default:
break;
}
}
m_graph.vote(node, VoteOther);
break;
}
case GarbageValue:
break;
default:
m_graph.vote(node, VoteOther);
break;
}
}
}
// Disable structure hoisting on variables that appear to mostly be used in
// contexts where it doesn't make sense.
for (unsigned i = m_graph.m_variableAccessData.size(); i--;) {
VariableAccessData* variable = &m_graph.m_variableAccessData[i];
if (!variable->isRoot())
continue;
if (variable->voteRatio() >= Options::structureCheckVoteRatioForHoisting())
continue;
HashMap<VariableAccessData*, CheckData>::iterator iter = m_map.find(variable);
if (iter == m_map.end())
continue;
#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
dataLog(
"Zeroing the structure to hoist for ", VariableAccessDataDump(m_graph, variable),
" because the ratio is ", variable->voteRatio(), ".\n");
#endif
iter->value.m_structure = 0;
}
// Disable structure check hoisting for variables that cross the OSR entry that
// we're currently taking, and where the value currently does not have the
// structure we want.
for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) {
BasicBlock* block = m_graph.m_blocks[blockIndex].get();
if (!block)
continue;
ASSERT(block->isReachable);
if (!block->isOSRTarget)
continue;
if (block->bytecodeBegin != m_graph.m_osrEntryBytecodeIndex)
continue;
for (size_t i = 0; i < m_graph.m_mustHandleValues.size(); ++i) {
int operand = m_graph.m_mustHandleValues.operandForIndex(i);
NodeIndex nodeIndex = block->variablesAtHead.operand(operand);
if (nodeIndex == NoNode)
continue;
VariableAccessData* variable = m_graph[nodeIndex].variableAccessData();
HashMap<VariableAccessData*, CheckData>::iterator iter = m_map.find(variable);
if (iter == m_map.end())
continue;
if (!iter->value.m_structure)
continue;
JSValue value = m_graph.m_mustHandleValues[i];
if (!value || !value.isCell()) {
#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
dataLog(
"Zeroing the structure to hoist for ", VariableAccessDataDump(m_graph, variable),
" because the OSR entry value is not a cell: ", value, ".\n");
#endif
iter->value.m_structure = 0;
continue;
}
if (value.asCell()->structure() != iter->value.m_structure) {
#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
dataLog(
"Zeroing the structure to hoist for ", VariableAccessDataDump(m_graph, variable),
" because the OSR entry value has structure ",
RawPointer(value.asCell()->structure()), " and we wanted ",
RawPointer(iter->value.m_structure), ".\n");
#endif
iter->value.m_structure = 0;
continue;
}
}
}
bool changed = false;
#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
for (HashMap<VariableAccessData*, CheckData>::iterator it = m_map.begin();
it != m_map.end(); ++it) {
if (!it->value.m_structure) {
dataLog(
"Not hoisting checks for ", VariableAccessDataDump(m_graph, it->key),
" because of heuristics.\n");
continue;
}
dataLog("Hoisting checks for ", VariableAccessDataDump(m_graph, it->key), "\n");
}
#endif // DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
// Place CheckStructure's at SetLocal sites.
InsertionSet<NodeIndex> insertionSet;
for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) {
BasicBlock* block = m_graph.m_blocks[blockIndex].get();
if (!block)
continue;
for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) {
NodeIndex nodeIndex = block->at(indexInBlock);
Node& node = m_graph[nodeIndex];
// Be careful not to use 'node' after appending to the graph. In those switch
// cases where we need to append, we first carefully extract everything we need
// from the node, before doing any appending.
if (!node.shouldGenerate())
continue;
switch (node.op()) {
case SetArgument: {
ASSERT(!blockIndex);
// Insert a GetLocal and a CheckStructure immediately following this
// SetArgument, if the variable was a candidate for structure hoisting.
// If the basic block previously only had the SetArgument as its
// variable-at-tail, then replace it with this GetLocal.
VariableAccessData* variable = node.variableAccessData();
HashMap<VariableAccessData*, CheckData>::iterator iter = m_map.find(variable);
if (iter == m_map.end())
break;
if (!iter->value.m_structure)
break;
node.ref();
CodeOrigin codeOrigin = node.codeOrigin;
Node getLocal(GetLocal, codeOrigin, OpInfo(variable), nodeIndex);
getLocal.predict(variable->prediction());
getLocal.ref();
NodeIndex getLocalIndex = m_graph.size();
m_graph.append(getLocal);
insertionSet.append(indexInBlock + 1, getLocalIndex);
Node checkStructure(CheckStructure, codeOrigin, OpInfo(m_graph.addStructureSet(iter->value.m_structure)), getLocalIndex);
checkStructure.ref();
NodeIndex checkStructureIndex = m_graph.size();
m_graph.append(checkStructure);
insertionSet.append(indexInBlock + 1, checkStructureIndex);
if (block->variablesAtTail.operand(variable->local()) == nodeIndex)
block->variablesAtTail.operand(variable->local()) = getLocalIndex;
m_graph.substituteGetLocal(*block, indexInBlock, variable, getLocalIndex);
changed = true;
break;
}
case SetLocal: {
VariableAccessData* variable = node.variableAccessData();
HashMap<VariableAccessData*, CheckData>::iterator iter = m_map.find(variable);
if (iter == m_map.end())
break;
if (!iter->value.m_structure)
break;
// First insert a dead SetLocal to tell OSR that the child's value should
// be dropped into this bytecode variable if the CheckStructure decides
// to exit.
CodeOrigin codeOrigin = node.codeOrigin;
NodeIndex child1 = node.child1().index();
Node setLocal(SetLocal, codeOrigin, OpInfo(variable), child1);
NodeIndex setLocalIndex = m_graph.size();
m_graph.append(setLocal);
insertionSet.append(indexInBlock, setLocalIndex);
m_graph[child1].ref();
// Use a ForwardCheckStructure to indicate that we should exit to the
// next bytecode instruction rather than reexecuting the current one.
Node checkStructure(ForwardCheckStructure, codeOrigin, OpInfo(m_graph.addStructureSet(iter->value.m_structure)), child1);
checkStructure.ref();
NodeIndex checkStructureIndex = m_graph.size();
m_graph.append(checkStructure);
insertionSet.append(indexInBlock, checkStructureIndex);
changed = true;
break;
}
default:
break;
}
}
insertionSet.execute(*block);
}
return changed;
}
private:
void noticeStructureCheck(VariableAccessData* variable, Structure* structure)
{
HashMap<VariableAccessData*, CheckData>::AddResult result =
m_map.add(variable, CheckData(structure));
if (result.isNewEntry)
return;
if (result.iterator->value.m_structure == structure)
return;
result.iterator->value.m_structure = 0;
}
void noticeStructureCheck(VariableAccessData* variable, const StructureSet& set)
{
if (set.size() != 1) {
noticeStructureCheck(variable, 0);
return;
}
noticeStructureCheck(variable, set.singletonStructure());
}
struct CheckData {
Structure* m_structure;
CheckData()
: m_structure(0)
{
}
CheckData(Structure* structure)
: m_structure(structure)
{
}
};
HashMap<VariableAccessData*, CheckData> m_map;
};
bool performStructureCheckHoisting(Graph& graph)
{
SamplingRegion samplingRegion("DFG Structure Check Hoisting Phase");
return runPhase<StructureCheckHoistingPhase>(graph);
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)