mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2024-11-23 04:09:40 +00:00
510 lines
18 KiB
C++
510 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2015-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 "B3ReduceDoubleToFloat.h"
|
|
|
|
#if ENABLE(B3_JIT)
|
|
|
|
#include "B3BasicBlock.h"
|
|
#include "B3InsertionSetInlines.h"
|
|
#include "B3PhaseScope.h"
|
|
#include "B3UseCounts.h"
|
|
#include "B3ValueInlines.h"
|
|
#include <wtf/IndexSet.h>
|
|
|
|
namespace JSC { namespace B3 {
|
|
|
|
namespace {
|
|
|
|
namespace B3ReduceDoubleToFloatInternal {
|
|
static constexpr bool verbose = false;
|
|
}
|
|
bool printRemainingConversions = false;
|
|
|
|
class DoubleToFloatReduction {
|
|
public:
|
|
DoubleToFloatReduction(Procedure& procedure)
|
|
: m_procedure(procedure)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
if (!findCandidates())
|
|
return;
|
|
|
|
findPhisContainingFloat();
|
|
|
|
simplify();
|
|
|
|
cleanUp();
|
|
}
|
|
|
|
private:
|
|
// This step find values that are used as Double and cannot be converted to Float..
|
|
// It flows the information backward through Phi-Upsilons.
|
|
bool findCandidates()
|
|
{
|
|
bool foundConversionCandidate = false;
|
|
Vector<Value*, 32> upsilons;
|
|
|
|
// First, we find all values that are strictly used as double.
|
|
// Those are values used by something else than DoubleToFloat.
|
|
//
|
|
// We don't know the state of Upsilons until their Phi has been
|
|
// set. We just keep a list of them and update them next.
|
|
for (BasicBlock* block : m_procedure) {
|
|
for (Value* value : *block) {
|
|
value->performSubstitution();
|
|
|
|
if (value->opcode() == DoubleToFloat) {
|
|
foundConversionCandidate = true;
|
|
|
|
Value* child = value->child(0);
|
|
if (child->opcode() == FloatToDouble) {
|
|
// We don't really need to simplify this early but it simplifies debugging.
|
|
value->replaceWithIdentity(child->child(0));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (value->opcode() == FloatToDouble)
|
|
foundConversionCandidate = true;
|
|
|
|
if (value->opcode() == Upsilon) {
|
|
Value* child = value->child(0);
|
|
if (child->type() == Double)
|
|
upsilons.append(value);
|
|
continue;
|
|
}
|
|
|
|
for (Value* child : value->children()) {
|
|
if (child->type() == Double)
|
|
m_valuesUsedAsDouble.add(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundConversionCandidate)
|
|
return false;
|
|
|
|
// Now we just need to propagate through Phi-Upsilon.
|
|
// A Upsilon can convert its input to float if its phi is never used as double.
|
|
// If we modify a phi, we need to continue until all the Upsilon-Phi converge.
|
|
bool changedPhiState;
|
|
do {
|
|
changedPhiState = false;
|
|
for (Value* value : upsilons) {
|
|
UpsilonValue* upsilon = value->as<UpsilonValue>();
|
|
Value* phi = upsilon->phi();
|
|
if (!m_valuesUsedAsDouble.contains(phi))
|
|
continue;
|
|
|
|
Value* child = value->child(0);
|
|
bool childChanged = m_valuesUsedAsDouble.add(child);
|
|
if (childChanged && child->opcode() == Phi)
|
|
changedPhiState = true;
|
|
}
|
|
} while (changedPhiState);
|
|
|
|
if (B3ReduceDoubleToFloatInternal::verbose) {
|
|
dataLog("Conversion candidates:\n");
|
|
for (BasicBlock* block : m_procedure) {
|
|
for (Value* value : *block) {
|
|
if (value->type() == Double && !m_valuesUsedAsDouble.contains(value))
|
|
dataLog(" ", deepDump(m_procedure, value), "\n");
|
|
}
|
|
}
|
|
dataLog("\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// This step finds Phis of type Double that effectively contains Float values.
|
|
// It flows that information forward through Phi-Upsilons.
|
|
void findPhisContainingFloat()
|
|
{
|
|
Vector<Value*, 32> upsilons;
|
|
|
|
// The Double value that can be safely turned into a Float are:
|
|
// - FloatToDouble
|
|
// - ConstDouble with a value that converts to Float without losing precision.
|
|
for (BasicBlock* block : m_procedure) {
|
|
for (Value* value : *block) {
|
|
if (value->opcode() != Upsilon)
|
|
continue;
|
|
|
|
Value* child = value->child(0);
|
|
if (child->type() != Double
|
|
|| child->opcode() == FloatToDouble)
|
|
continue;
|
|
|
|
if (child->hasDouble()) {
|
|
double constValue = child->asDouble();
|
|
if (isIdentical(static_cast<double>(static_cast<float>(constValue)), constValue))
|
|
continue;
|
|
}
|
|
|
|
if (child->opcode() == Phi) {
|
|
upsilons.append(value);
|
|
continue;
|
|
}
|
|
|
|
UpsilonValue* upsilon = value->as<UpsilonValue>();
|
|
Value* phi = upsilon->phi();
|
|
m_phisContainingDouble.add(phi);
|
|
}
|
|
}
|
|
|
|
// Propagate the flags forward.
|
|
bool changedPhiState;
|
|
do {
|
|
changedPhiState = false;
|
|
for (Value* value : upsilons) {
|
|
Value* child = value->child(0);
|
|
if (m_phisContainingDouble.contains(child)) {
|
|
UpsilonValue* upsilon = value->as<UpsilonValue>();
|
|
Value* phi = upsilon->phi();
|
|
changedPhiState |= m_phisContainingDouble.add(phi);
|
|
}
|
|
}
|
|
} while (changedPhiState);
|
|
|
|
if (B3ReduceDoubleToFloatInternal::verbose) {
|
|
dataLog("Phis containing float values:\n");
|
|
for (BasicBlock* block : m_procedure) {
|
|
for (Value* value : *block) {
|
|
if (value->opcode() == Phi
|
|
&& value->type() == Double
|
|
&& !m_phisContainingDouble.contains(value))
|
|
dataLog(" ", deepDump(m_procedure, value), "\n");
|
|
}
|
|
}
|
|
dataLog("\n");
|
|
}
|
|
}
|
|
|
|
bool canBeTransformedToFloat(Value* value)
|
|
{
|
|
if (value->opcode() == FloatToDouble)
|
|
return true;
|
|
|
|
if (value->hasDouble())
|
|
return true; // Double constant truncated to float.
|
|
|
|
if (value->opcode() == Phi) {
|
|
return value->type() == Float
|
|
|| (value->type() == Double && !m_phisContainingDouble.contains(value));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Value* transformToFloat(Value* value, unsigned valueIndex, InsertionSet& insertionSet)
|
|
{
|
|
ASSERT(canBeTransformedToFloat(value));
|
|
if (value->opcode() == FloatToDouble)
|
|
return value->child(0);
|
|
|
|
if (value->hasDouble())
|
|
return insertionSet.insert<ConstFloatValue>(valueIndex, value->origin(), static_cast<float>(value->asDouble()));
|
|
|
|
if (value->opcode() == Phi) {
|
|
ASSERT(value->type() == Double || value->type() == Float);
|
|
if (value->type() == Double)
|
|
convertPhi(value);
|
|
return value;
|
|
}
|
|
RELEASE_ASSERT_NOT_REACHED();
|
|
return nullptr;
|
|
}
|
|
|
|
void convertPhi(Value* phi)
|
|
{
|
|
ASSERT(phi->opcode() == Phi);
|
|
ASSERT(phi->type() == Double);
|
|
phi->setType(Float);
|
|
m_convertedPhis.add(phi);
|
|
}
|
|
|
|
bool attemptTwoOperandsSimplify(Value* candidate, unsigned candidateIndex, InsertionSet& insertionSet)
|
|
{
|
|
Value* left = candidate->child(0);
|
|
Value* right = candidate->child(1);
|
|
if (!canBeTransformedToFloat(left) || !canBeTransformedToFloat(right))
|
|
return false;
|
|
|
|
if (left->hasDouble() && right->hasDouble()) {
|
|
// If both inputs are constants, converting them to floats and performing
|
|
// the same operation is incorrect. It may produce a different value
|
|
// depending on the operation and the inputs. There are inputs where
|
|
// casting to float and performing the operation would result in the
|
|
// same value. Regardless, we don't prove when that is legal here since
|
|
// it isn't profitable to do. We leave it to strength reduction to handle
|
|
// reduce these cases.
|
|
return false;
|
|
}
|
|
|
|
m_convertedValue.add(candidate);
|
|
candidate->child(0) = transformToFloat(left, candidateIndex, insertionSet);
|
|
candidate->child(1) = transformToFloat(right, candidateIndex, insertionSet);
|
|
return true;
|
|
}
|
|
|
|
// Simplify Double operations into Float operations.
|
|
void simplify()
|
|
{
|
|
Vector<Value*, 32> upsilonReferencingDoublePhi;
|
|
|
|
InsertionSet insertionSet(m_procedure);
|
|
for (BasicBlock* block : m_procedure) {
|
|
for (unsigned index = 0; index < block->size(); ++index) {
|
|
Value* value = block->at(index);
|
|
|
|
switch (value->opcode()) {
|
|
case Equal:
|
|
case NotEqual:
|
|
case LessThan:
|
|
case GreaterThan:
|
|
case LessEqual:
|
|
case GreaterEqual:
|
|
case EqualOrUnordered:
|
|
attemptTwoOperandsSimplify(value, index, insertionSet);
|
|
continue;
|
|
case Upsilon: {
|
|
Value* child = value->child(0);
|
|
if (child->opcode() == Phi && child->type() == Double)
|
|
upsilonReferencingDoublePhi.append(value);
|
|
continue;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (m_valuesUsedAsDouble.contains(value))
|
|
continue;
|
|
|
|
switch (value->opcode()) {
|
|
case Add:
|
|
case Sub:
|
|
case Mul:
|
|
case Div:
|
|
if (attemptTwoOperandsSimplify(value, index, insertionSet))
|
|
value->setType(Float);
|
|
break;
|
|
case Abs:
|
|
case Ceil:
|
|
case Floor:
|
|
case Neg:
|
|
case Sqrt: {
|
|
Value* child = value->child(0);
|
|
if (canBeTransformedToFloat(child)) {
|
|
value->child(0) = transformToFloat(child, index, insertionSet);
|
|
value->setType(Float);
|
|
m_convertedValue.add(value);
|
|
}
|
|
break;
|
|
}
|
|
case IToD: {
|
|
Value* iToF = insertionSet.insert<Value>(index, IToF, value->origin(), value->child(0));
|
|
value->setType(Float);
|
|
value->replaceWithIdentity(iToF);
|
|
m_convertedValue.add(value);
|
|
break;
|
|
}
|
|
case FloatToDouble:
|
|
// This happens if we round twice.
|
|
// Typically, this is indirect through Phi-Upsilons.
|
|
// The Upsilon rounds and the Phi rounds.
|
|
value->setType(Float);
|
|
value->replaceWithIdentity(value->child(0));
|
|
m_convertedValue.add(value);
|
|
break;
|
|
case Phi:
|
|
// If a Phi is always converted to Float, we always make it into a float Phi-Upsilon.
|
|
// This is a simplistic view of things. Ideally we should keep type that will minimize
|
|
// the amount of conversion in the loop.
|
|
if (value->type() == Double)
|
|
convertPhi(value);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
insertionSet.execute(block);
|
|
}
|
|
|
|
if (!upsilonReferencingDoublePhi.isEmpty()) {
|
|
// If a Phi contains Float values typed as Double, but is not used as Float
|
|
// by a non-trivial operation, we did not convert it.
|
|
//
|
|
// We fix that now by converting the remaining phis that contains
|
|
// float but where not converted to float.
|
|
bool changedPhi;
|
|
do {
|
|
changedPhi = false;
|
|
|
|
for (Value* value : upsilonReferencingDoublePhi) {
|
|
UpsilonValue* upsilon = value->as<UpsilonValue>();
|
|
Value* child = value->child(0);
|
|
Value* phi = upsilon->phi();
|
|
if (phi->type() == Float && child->type() == Double
|
|
&& !m_phisContainingDouble.contains(child)) {
|
|
convertPhi(child);
|
|
changedPhi = true;
|
|
}
|
|
}
|
|
|
|
} while (changedPhi);
|
|
}
|
|
}
|
|
|
|
// We are in an inconsistent state where we have
|
|
// DoubleToFloat nodes over values producing float and Phis that are
|
|
// float for Upsilons that are Double.
|
|
//
|
|
// This steps puts us back in a consistent state.
|
|
void cleanUp()
|
|
{
|
|
InsertionSet insertionSet(m_procedure);
|
|
|
|
for (BasicBlock* block : m_procedure) {
|
|
for (unsigned index = 0; index < block->size(); ++index) {
|
|
Value* value = block->at(index);
|
|
if (value->opcode() == DoubleToFloat && value->child(0)->type() == Float) {
|
|
value->replaceWithIdentity(value->child(0));
|
|
continue;
|
|
}
|
|
|
|
if (value->opcode() == Upsilon) {
|
|
UpsilonValue* upsilon = value->as<UpsilonValue>();
|
|
Value* child = value->child(0);
|
|
Value* phi = upsilon->phi();
|
|
|
|
if (phi->type() == Float) {
|
|
if (child->type() == Double) {
|
|
Value* newChild = nullptr;
|
|
if (child->opcode() == FloatToDouble)
|
|
newChild = child->child(0);
|
|
else if (child->hasDouble())
|
|
newChild = insertionSet.insert<ConstFloatValue>(index, child->origin(), static_cast<float>(child->asDouble()));
|
|
else
|
|
newChild = insertionSet.insert<Value>(index, DoubleToFloat, upsilon->origin(), child);
|
|
upsilon->child(0) = newChild;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!m_convertedValue.contains(value)) {
|
|
// Phis can be converted from Double to Float if the value they contain
|
|
// is not more precise than a Float.
|
|
// If the value is needed as Double, it has to be converted back.
|
|
for (Value*& child : value->children()) {
|
|
if (m_convertedPhis.contains(child))
|
|
child = insertionSet.insert<Value>(index, FloatToDouble, value->origin(), child);
|
|
}
|
|
}
|
|
}
|
|
insertionSet.execute(block);
|
|
}
|
|
}
|
|
|
|
Procedure& m_procedure;
|
|
|
|
// Set of all the Double values that are actually used as Double.
|
|
// Converting any of them to Float would lose precision.
|
|
IndexSet<Value*> m_valuesUsedAsDouble;
|
|
|
|
// Set of all the Phi of type Double that really contains a Double.
|
|
// Any Double Phi not in the set can be converted to Float without losing precision.
|
|
IndexSet<Value*> m_phisContainingDouble;
|
|
|
|
// Any value that was converted from producing a Double to producing a Float.
|
|
// This set does not include Phi-Upsilons.
|
|
IndexSet<Value*> m_convertedValue;
|
|
|
|
// Any value that previously produced Double and now produce Float.
|
|
IndexSet<Value*> m_convertedPhis;
|
|
};
|
|
|
|
void printGraphIfConverting(Procedure& procedure)
|
|
{
|
|
if (!printRemainingConversions)
|
|
return;
|
|
|
|
UseCounts useCount(procedure);
|
|
|
|
Vector<Value*> doubleToFloat;
|
|
Vector<Value*> floatToDouble;
|
|
|
|
for (BasicBlock* block : procedure) {
|
|
for (Value* value : *block) {
|
|
if (!useCount.numUses(value))
|
|
continue;
|
|
|
|
if (value->opcode() == DoubleToFloat)
|
|
doubleToFloat.append(value);
|
|
if (value->opcode() == FloatToDouble)
|
|
floatToDouble.append(value);
|
|
}
|
|
}
|
|
|
|
if (doubleToFloat.isEmpty() && floatToDouble.isEmpty())
|
|
return;
|
|
|
|
dataLog("Procedure with Float-Double conversion:\n", procedure, "\n");
|
|
dataLog("Converting nodes:\n");
|
|
for (Value* value : doubleToFloat)
|
|
dataLog(" ", deepDump(procedure, value), "\n");
|
|
for (Value* value : floatToDouble)
|
|
dataLog(" ", deepDump(procedure, value), "\n");
|
|
|
|
}
|
|
|
|
} // anonymous namespace.
|
|
|
|
void reduceDoubleToFloat(Procedure& procedure)
|
|
{
|
|
PhaseScope phaseScope(procedure, "reduceDoubleToFloat");
|
|
|
|
if (B3ReduceDoubleToFloatInternal::verbose)
|
|
dataLog("Before DoubleToFloatReduction:\n", procedure, "\n");
|
|
|
|
DoubleToFloatReduction doubleToFloatReduction(procedure);
|
|
doubleToFloatReduction.run();
|
|
|
|
if (B3ReduceDoubleToFloatInternal::verbose)
|
|
dataLog("After DoubleToFloatReduction:\n", procedure, "\n");
|
|
|
|
printGraphIfConverting(procedure);
|
|
}
|
|
|
|
} } // namespace JSC::B3
|
|
|
|
#endif // ENABLE(B3_JIT)
|
|
|