blob: 82d1b624f7434df8d9a6ef02f1c7d77b525652df [file] [log] [blame]
/*
* Copyright (C) 2019 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 "DFGValueRepReductionPhase.h"
#if ENABLE(DFG_JIT)
#include "DFGGraph.h"
#include "DFGInsertionSet.h"
#include "DFGPhase.h"
#include "DFGPhiChildren.h"
namespace JSC { namespace DFG {
class ValueRepReductionPhase : public Phase {
static constexpr bool verbose = false;
public:
ValueRepReductionPhase(Graph& graph)
: Phase(graph, "ValueRep reduction")
, m_insertionSet(graph)
{
}
bool run()
{
ASSERT(m_graph.m_form == SSA);
return convertValueRepsToDouble();
}
private:
bool convertValueRepsToDouble()
{
HashSet<Node*> candidates;
for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
for (Node* node : *block) {
if (node->op() == ValueRep && node->child1().useKind() == DoubleRepUse)
candidates.add(node);
}
}
if (!candidates.size())
return false;
HashMap<Node*, Vector<Node*>> usersOf;
auto getUsersOf = [&] (Node* candidate) {
auto iter = usersOf.find(candidate);
RELEASE_ASSERT(iter != usersOf.end());
return iter->value;
};
for (BasicBlock* block : m_graph.blocksInPreOrder()) {
for (Node* node : *block) {
if (node->op() == Phi || (node->op() == ValueRep && candidates.contains(node)))
usersOf.add(node, Vector<Node*>());
Vector<Node*, 3> alreadyAdded;
m_graph.doToChildren(node, [&] (Edge edge) {
Node* candidate = edge.node();
if (alreadyAdded.contains(candidate))
return;
auto iter = usersOf.find(candidate);
if (iter == usersOf.end())
return;
iter->value.append(node);
alreadyAdded.append(candidate);
});
}
}
PhiChildren phiChildren(m_graph);
// - Any candidate that forwards into a Phi turns that Phi into a candidate.
// - Any Phi-1 that forwards into another Phi-2, where Phi-2 is a candidate,
// makes Phi-1 a candidate too.
do {
HashSet<Node*> eligiblePhis;
for (Node* candidate : candidates) {
if (candidate->op() == Phi) {
phiChildren.forAllIncomingValues(candidate, [&] (Node* incoming) {
if (incoming->op() == Phi)
eligiblePhis.add(incoming);
});
}
for (Node* user : getUsersOf(candidate)) {
if (user->op() == Upsilon)
eligiblePhis.add(user->phi());
}
}
bool sawNewCandidate = false;
for (Node* phi : eligiblePhis)
sawNewCandidate |= candidates.add(phi).isNewEntry;
if (!sawNewCandidate)
break;
} while (true);
do {
HashSet<Node*> toRemove;
auto isEscaped = [&] (Node* node) {
return !candidates.contains(node) || toRemove.contains(node);
};
// Escape rules are as follows:
// - Any non-well-known use is an escape. Currently, we allow DoubleRep, Hints, Upsilons (described below).
// - Any Upsilon that forwards the candidate into an escaped phi escapes the candidate.
// - A Phi remains a candidate as long as all values flowing into it can be made a double.
// Currently, this means these are valid things we support to forward into the Phi:
// - A JSConstant(some number "x") => DoubleConstant("x")
// - ValueRep(DoubleRepUse:@x) => @x
// - A Phi so long as that Phi is not escaped.
for (Node* candidate : candidates) {
bool ok = true;
auto dumpEscape = [&] (const char* description, Node* node) {
if (!verbose)
return;
dataLogLn(description);
dataLog(" candidate: ");
m_graph.dump(WTF::dataFile(), Prefix::noString, candidate);
dataLog(" reason: ");
m_graph.dump(WTF::dataFile(), Prefix::noString, node);
dataLogLn();
};
if (candidate->op() == Phi) {
phiChildren.forAllIncomingValues(candidate, [&] (Node* node) {
if (node->op() == JSConstant) {
if (!node->asJSValue().isNumber()) {
ok = false;
dumpEscape("Phi Incoming JSConstant not a number: ", node);
}
} else if (node->op() == ValueRep) {
if (node->child1().useKind() != DoubleRepUse) {
ok = false;
dumpEscape("Phi Incoming ValueRep not DoubleRepUse: ", node);
}
} else if (node->op() == Phi) {
if (isEscaped(node)) {
ok = false;
dumpEscape("An incoming Phi to another Phi is escaped: ", node);
}
} else {
ok = false;
dumpEscape("Unsupported incoming value to Phi: ", node);
}
});
if (!ok) {
toRemove.add(candidate);
continue;
}
}
for (Node* user : getUsersOf(candidate)) {
switch (user->op()) {
case DoubleRep:
if (user->child1().useKind() != RealNumberUse) {
ok = false;
dumpEscape("DoubleRep escape: ", user);
}
break;
case PutHint:
case MovHint:
break;
case Upsilon: {
Node* phi = user->phi();
if (isEscaped(phi)) {
dumpEscape("Upsilon of escaped phi escapes candidate: ", phi);
ok = false;
}
break;
}
default:
dumpEscape("Normal escape: ", user);
ok = false;
break;
}
if (!ok)
break;
}
if (!ok)
toRemove.add(candidate);
}
if (toRemove.isEmpty())
break;
for (Node* node : toRemove)
candidates.remove(node);
} while (true);
if (!candidates.size())
return false;
NodeOrigin originForConstant = m_graph.block(0)->at(0)->origin;
HashSet<Node*> doubleRepRealCheckLocations;
for (Node* candidate : candidates) {
if (verbose)
dataLogLn("Optimized: ", candidate);
Node* resultNode;
if (candidate->op() == Phi) {
resultNode = candidate;
for (Node* upsilon : phiChildren.upsilonsOf(candidate)) {
Node* incomingValue = upsilon->child1().node();
Node* newChild;
if (incomingValue->op() == JSConstant) {
double number = incomingValue->asJSValue().asNumber();
newChild = m_insertionSet.insertConstant(0, originForConstant, jsDoubleNumber(number), DoubleConstant);
} else if (incomingValue->op() == ValueRep) {
// We don't care about the incoming value being an impure NaN because users of
// this Phi are either OSR exit or DoubleRep(RealNumberUse:@phi)
ASSERT(incomingValue->child1().useKind() == DoubleRepUse);
newChild = incomingValue->child1().node();
} else if (incomingValue->op() == Phi)
newChild = incomingValue;
else
RELEASE_ASSERT_NOT_REACHED();
upsilon->child1() = Edge(newChild, DoubleRepUse);
}
candidate->setResult(NodeResultDouble);
} else if (candidate->op() == ValueRep)
resultNode = candidate->child1().node();
else
RELEASE_ASSERT_NOT_REACHED();
for (Node* user : getUsersOf(candidate)) {
switch (user->op()) {
case DoubleRep: {
ASSERT(user->child1().useKind() == RealNumberUse);
user->convertToIdentityOn(resultNode);
doubleRepRealCheckLocations.add(user);
break;
}
case PutHint:
user->child2() = Edge(resultNode, DoubleRepUse);
break;
case MovHint:
user->child1() = Edge(resultNode, DoubleRepUse);
break;
case Upsilon: {
Node* phi = user->phi();
ASSERT_UNUSED(phi, candidates.contains(phi));
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
}
}
// Insert constants.
m_insertionSet.execute(m_graph.block(0));
// Insert checks that are needed when removing DoubleRep(RealNumber:@x)
if (doubleRepRealCheckLocations.size()) {
for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
for (unsigned i = 0; i < block->size(); ++i) {
Node* node = block->at(i);
if (node->op() != Identity) {
ASSERT(!doubleRepRealCheckLocations.contains(node));
continue;
}
if (!doubleRepRealCheckLocations.contains(node))
continue;
m_insertionSet.insertNode(
i, SpecNone, Check, node->origin,
Edge(node->child1().node(), DoubleRepRealUse));
}
m_insertionSet.execute(block);
}
}
return true;
}
InsertionSet m_insertionSet;
};
bool performValueRepReduction(Graph& graph)
{
return runPhase<ValueRepReductionPhase>(graph);
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)