darling-JavaScriptCore/dfg/DFGIntegerCheckCombiningPhase.cpp

397 lines
14 KiB
C++

/*
* Copyright (C) 2014-2020 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 "DFGIntegerCheckCombiningPhase.h"
#if ENABLE(DFG_JIT)
#include "DFGGraph.h"
#include "DFGInsertionSet.h"
#include "DFGPhase.h"
#include "JSCJSValueInlines.h"
namespace JSC { namespace DFG {
namespace DFGIntegerCheckCombiningPhaseInternal {
static constexpr bool verbose = false;
}
class IntegerCheckCombiningPhase : public Phase {
public:
enum RangeKind {
InvalidRangeKind,
// This means we did ArithAdd with CheckOverflow.
Addition,
// This means we did CheckInBounds on some length.
ArrayBounds
};
struct RangeKey {
static RangeKey addition(Edge edge)
{
RangeKey result;
result.m_kind = Addition;
result.m_source = edge.sanitized();
result.m_key = nullptr;
return result;
}
static RangeKey arrayBounds(Edge edge, Node* key)
{
RangeKey result;
result.m_kind = ArrayBounds;
result.m_source = edge.sanitized();
result.m_key = key;
return result;
}
bool operator!() const { return m_kind == InvalidRangeKind; }
unsigned hash() const
{
return m_kind + m_source.hash() + PtrHash<Node*>::hash(m_key);
}
bool operator==(const RangeKey& other) const
{
return m_kind == other.m_kind
&& m_source == other.m_source
&& m_key == other.m_key;
}
void dump(PrintStream& out) const
{
switch (m_kind) {
case InvalidRangeKind:
out.print("InvalidRangeKind(");
break;
case Addition:
out.print("Addition(");
break;
case ArrayBounds:
out.print("ArrayBounds(");
break;
}
if (m_source)
out.print(m_source);
else
out.print("null");
out.print(", ");
if (m_key)
out.print(m_key);
else
out.print("null");
out.print(")");
}
RangeKind m_kind { InvalidRangeKind };
Edge m_source;
Node* m_key { nullptr };
};
struct RangeKeyAndAddend {
RangeKeyAndAddend() = default;
RangeKeyAndAddend(RangeKey key, int32_t addend)
: m_key(key)
, m_addend(addend)
{
}
bool operator!() const { return !m_key && !m_addend; }
void dump(PrintStream& out) const
{
out.print(m_key, " + ", m_addend);
}
RangeKey m_key;
int32_t m_addend { 0 };
};
struct Range {
void dump(PrintStream& out) const
{
out.print("(", m_minBound, " @", m_minOrigin, ") .. (", m_maxBound, " @", m_maxOrigin, "), count = ", m_count, ", hoisted = ", m_hoisted);
}
int32_t m_minBound { 0 };
int32_t m_maxBound { 0 };
CodeOrigin m_minOrigin;
CodeOrigin m_maxOrigin;
unsigned m_count { 0 }; // If this is zero then the bounds won't necessarily make sense.
bool m_hoisted { false };
Node* m_dependency { nullptr };
};
IntegerCheckCombiningPhase(Graph& graph)
: Phase(graph, "integer check combining")
, m_insertionSet(graph)
{
}
bool run()
{
ASSERT(m_graph.m_form == SSA);
m_changed = false;
for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;)
handleBlock(blockIndex);
return m_changed;
}
private:
void handleBlock(BlockIndex blockIndex)
{
BasicBlock* block = m_graph.block(blockIndex);
if (!block)
return;
m_map.clear();
// First we collect Ranges. If operations within the range have enough redundancy,
// we hoist. And then we remove additions and checks that fall within the max range.
for (auto* node : *block) {
RangeKeyAndAddend data = rangeKeyAndAddend(node);
if (DFGIntegerCheckCombiningPhaseInternal::verbose)
dataLog("For ", node, ": ", data, "\n");
if (!data)
continue;
Range& range = m_map[data.m_key];
if (DFGIntegerCheckCombiningPhaseInternal::verbose)
dataLog(" Range: ", range, "\n");
if (range.m_count) {
if (data.m_addend > range.m_maxBound) {
range.m_maxBound = data.m_addend;
range.m_maxOrigin = node->origin.semantic;
} else if (data.m_addend < range.m_minBound) {
range.m_minBound = data.m_addend;
range.m_minOrigin = node->origin.semantic;
}
} else {
range.m_maxBound = data.m_addend;
range.m_minBound = data.m_addend;
range.m_minOrigin = node->origin.semantic;
range.m_maxOrigin = node->origin.semantic;
}
range.m_count++;
if (DFGIntegerCheckCombiningPhaseInternal::verbose)
dataLog(" New range: ", range, "\n");
}
for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
Node* node = block->at(nodeIndex);
RangeKeyAndAddend data = rangeKeyAndAddend(node);
if (!data)
continue;
Range range = m_map[data.m_key];
if (!isValid(data.m_key, range))
continue;
// Do the hoisting.
if (!range.m_hoisted) {
NodeOrigin minOrigin = node->origin.withSemantic(range.m_minOrigin);
NodeOrigin maxOrigin = node->origin.withSemantic(range.m_maxOrigin);
switch (data.m_key.m_kind) {
case Addition: {
if (range.m_minBound < 0)
insertAdd(nodeIndex, minOrigin, data.m_key.m_source, range.m_minBound);
if (range.m_maxBound > 0)
insertAdd(nodeIndex, maxOrigin, data.m_key.m_source, range.m_maxBound);
break;
}
case ArrayBounds: {
Node* minNode;
Node* maxNode;
if (!data.m_key.m_source) {
// data.m_key.m_source being null means that we're comparing against int32 constants (see rangeKeyAndAddend()).
// Since CheckInBounds does an unsigned comparison, if the minBound >= 0, it is also covered by the
// maxBound comparison. However, if minBound < 0, then CheckInBounds should always fail its speculation check.
// We'll force an OSR exit in that case.
minNode = nullptr;
if (range.m_minBound < 0)
m_insertionSet.insertNode(nodeIndex, SpecNone, ForceOSRExit, node->origin);
maxNode = m_insertionSet.insertConstant(
nodeIndex, maxOrigin, jsNumber(range.m_maxBound));
} else {
minNode = insertAdd(
nodeIndex, minOrigin, data.m_key.m_source, range.m_minBound,
Arith::Unchecked);
maxNode = insertAdd(
nodeIndex, maxOrigin, data.m_key.m_source, range.m_maxBound,
Arith::Unchecked);
}
Node* minCheck = nullptr;
if (minNode) {
minCheck = m_insertionSet.insertNode(
nodeIndex, SpecNone, CheckInBounds, node->origin,
Edge(minNode, Int32Use), Edge(data.m_key.m_key, Int32Use));
}
m_map[data.m_key].m_dependency = m_insertionSet.insertNode(
nodeIndex, SpecNone, CheckInBounds, node->origin,
Edge(maxNode, Int32Use), Edge(data.m_key.m_key, Int32Use), Edge(minCheck, UntypedUse));
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
m_changed = true;
m_map[data.m_key].m_hoisted = true;
}
// Do the elimination.
switch (data.m_key.m_kind) {
case Addition:
node->setArithMode(Arith::Unchecked);
m_changed = true;
break;
case ArrayBounds:
ASSERT(node->op() == CheckInBounds);
if (UNLIKELY(Options::validateBoundsCheckElimination()))
m_insertionSet.insertNode(nodeIndex, SpecNone, AssertInBounds, node->origin, node->child1(), node->child2());
node->convertToIdentityOn(m_map[data.m_key].m_dependency);
m_changed = true;
break;
default:
RELEASE_ASSERT_NOT_REACHED();
}
}
m_insertionSet.execute(block);
}
RangeKeyAndAddend rangeKeyAndAddend(Node* node)
{
switch (node->op()) {
case ArithAdd: {
if (node->arithMode() != Arith::CheckOverflow
&& node->arithMode() != Arith::CheckOverflowAndNegativeZero)
break;
if (!node->child2()->isInt32Constant())
break;
return RangeKeyAndAddend(
RangeKey::addition(node->child1()),
node->child2()->asInt32());
}
case CheckInBounds: {
Edge source;
int32_t addend;
Node* key = node->child2().node();
Edge index = node->child1();
if (index->isInt32Constant()) {
source = Edge();
addend = index->asInt32();
} else if (
index->op() == ArithAdd
&& index->isBinaryUseKind(Int32Use)
&& index->child2()->isInt32Constant()) {
source = index->child1();
addend = index->child2()->asInt32();
} else {
source = index;
addend = 0;
}
return RangeKeyAndAddend(RangeKey::arrayBounds(source, key), addend);
}
default:
break;
}
return RangeKeyAndAddend();
}
bool isValid(const RangeKey& key, const Range& range)
{
if (range.m_count < 2)
return false;
switch (key.m_kind) {
case ArrayBounds: {
// Have to do this carefully because C++ compilers are too smart. But all we're really doing is detecting if
// the difference between the bounds is 2^31 or more. If it was, then we'd have to worry about wrap-around.
// The way we'd like to write this expression is (range.m_maxBound - range.m_minBound) >= 0, but that is a
// signed subtraction and compare, which allows the C++ compiler to do anything it wants in case of
// wrap-around.
uint32_t maxBound = range.m_maxBound;
uint32_t minBound = range.m_minBound;
uint32_t unsignedDifference = maxBound - minBound;
return !(unsignedDifference >> 31);
}
default:
return true;
}
}
Node* insertAdd(
unsigned nodeIndex, NodeOrigin origin, Edge source, int32_t addend,
Arith::Mode arithMode = Arith::CheckOverflow)
{
if (!addend)
return source.node();
return m_insertionSet.insertNode(
nodeIndex, source->prediction(), source->result(),
ArithAdd, origin, OpInfo(arithMode), source,
m_insertionSet.insertConstantForUse(
nodeIndex, origin, jsNumber(addend), source.useKind()));
}
using RangeMap = StdUnorderedMap<RangeKey, Range, HashMethod<RangeKey>>;
RangeMap m_map;
InsertionSet m_insertionSet;
bool m_changed;
};
bool performIntegerCheckCombining(Graph& graph)
{
return runPhase<IntegerCheckCombiningPhase>(graph);
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)