mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2024-11-26 21:50:53 +00:00
332 lines
12 KiB
C++
332 lines
12 KiB
C++
/*
|
|
* 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"
|
|
#include "JSCJSValueInlines.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)
|
|
|