mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2024-11-23 04:09:40 +00:00
1038 lines
43 KiB
C++
1038 lines
43 KiB
C++
/*
|
|
* Copyright (C) 2013-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 "DFGStrengthReductionPhase.h"
|
|
|
|
#if ENABLE(DFG_JIT)
|
|
|
|
#include "ButterflyInlines.h"
|
|
#include "DFGAbstractHeap.h"
|
|
#include "DFGClobberize.h"
|
|
#include "DFGGraph.h"
|
|
#include "DFGInsertionSet.h"
|
|
#include "DFGPhase.h"
|
|
#include "MathCommon.h"
|
|
#include "RegExpObject.h"
|
|
#include "StringPrototype.h"
|
|
#include <cstdlib>
|
|
#include <wtf/text/StringBuilder.h>
|
|
|
|
namespace JSC { namespace DFG {
|
|
|
|
class StrengthReductionPhase : public Phase {
|
|
static constexpr bool verbose = false;
|
|
|
|
public:
|
|
StrengthReductionPhase(Graph& graph)
|
|
: Phase(graph, "strength reduction")
|
|
, m_insertionSet(graph)
|
|
{
|
|
}
|
|
|
|
bool run()
|
|
{
|
|
ASSERT(m_graph.m_fixpointState == FixpointNotConverged);
|
|
|
|
m_changed = false;
|
|
|
|
for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
|
|
m_block = m_graph.block(blockIndex);
|
|
if (!m_block)
|
|
continue;
|
|
for (m_nodeIndex = 0; m_nodeIndex < m_block->size(); ++m_nodeIndex) {
|
|
m_node = m_block->at(m_nodeIndex);
|
|
handleNode();
|
|
}
|
|
m_insertionSet.execute(m_block);
|
|
}
|
|
|
|
return m_changed;
|
|
}
|
|
|
|
private:
|
|
void handleNode()
|
|
{
|
|
switch (m_node->op()) {
|
|
case ArithBitOr:
|
|
handleCommutativity();
|
|
|
|
if (m_node->child1().useKind() != UntypedUse && m_node->child2()->isInt32Constant() && !m_node->child2()->asInt32()) {
|
|
convertToIdentityOverChild1();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ArithBitXor:
|
|
case ArithBitAnd:
|
|
handleCommutativity();
|
|
break;
|
|
|
|
case ArithBitLShift:
|
|
case ArithBitRShift:
|
|
case BitURShift:
|
|
if (m_node->child1().useKind() != UntypedUse && m_node->child2()->isInt32Constant() && !(m_node->child2()->asInt32() & 0x1f)) {
|
|
convertToIdentityOverChild1();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case UInt32ToNumber:
|
|
if (m_node->child1()->op() == BitURShift
|
|
&& m_node->child1()->child2()->isInt32Constant()
|
|
&& (m_node->child1()->child2()->asInt32() & 0x1f)
|
|
&& m_node->arithMode() != Arith::DoOverflow) {
|
|
m_node->convertToIdentity();
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ArithAdd:
|
|
handleCommutativity();
|
|
|
|
if (m_node->child2()->isInt32Constant() && !m_node->child2()->asInt32()) {
|
|
convertToIdentityOverChild1();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ValueMul:
|
|
case ValueBitOr:
|
|
case ValueBitAnd:
|
|
case ValueBitXor: {
|
|
// FIXME: we should maybe support the case where one operand is always HeapBigInt and the other is always BigInt32?
|
|
if (m_node->binaryUseKind() == AnyBigIntUse || m_node->binaryUseKind() == BigInt32Use || m_node->binaryUseKind() == HeapBigIntUse)
|
|
handleCommutativity();
|
|
break;
|
|
}
|
|
|
|
case ArithMul: {
|
|
handleCommutativity();
|
|
Edge& child2 = m_node->child2();
|
|
if (child2->isNumberConstant() && child2->asNumber() == 2) {
|
|
switch (m_node->binaryUseKind()) {
|
|
case DoubleRepUse:
|
|
// It is always valuable to get rid of a double multiplication by 2.
|
|
// We won't have half-register dependencies issues on x86 and we won't have to load the constants.
|
|
m_node->setOp(ArithAdd);
|
|
child2.setNode(m_node->child1().node());
|
|
m_changed = true;
|
|
break;
|
|
#if USE(JSVALUE64)
|
|
case Int52RepUse:
|
|
#endif
|
|
case Int32Use:
|
|
// For integers, we can only convert compatible modes.
|
|
// ArithAdd does handle do negative zero check for example.
|
|
if (m_node->arithMode() == Arith::CheckOverflow || m_node->arithMode() == Arith::Unchecked) {
|
|
m_node->setOp(ArithAdd);
|
|
child2.setNode(m_node->child1().node());
|
|
m_changed = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ArithSub:
|
|
if (m_node->child2()->isInt32Constant()
|
|
&& m_node->isBinaryUseKind(Int32Use)) {
|
|
int32_t value = m_node->child2()->asInt32();
|
|
if (value != INT32_MIN) {
|
|
m_node->setOp(ArithAdd);
|
|
m_node->child2().setNode(
|
|
m_insertionSet.insertConstant(
|
|
m_nodeIndex, m_node->origin, jsNumber(-value)));
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ArithPow:
|
|
if (m_node->child2()->isNumberConstant()) {
|
|
double yOperandValue = m_node->child2()->asNumber();
|
|
if (yOperandValue == 1) {
|
|
convertToIdentityOverChild1();
|
|
m_changed = true;
|
|
} else if (yOperandValue == 2) {
|
|
m_node->setOp(ArithMul);
|
|
m_node->child2() = m_node->child1();
|
|
m_changed = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ArithMod:
|
|
// On Integers
|
|
// In: ArithMod(ArithMod(x, const1), const2)
|
|
// Out: Identity(ArithMod(x, const1))
|
|
// if const1 <= const2.
|
|
if (m_node->binaryUseKind() == Int32Use
|
|
&& m_node->child2()->isInt32Constant()
|
|
&& m_node->child1()->op() == ArithMod
|
|
&& m_node->child1()->binaryUseKind() == Int32Use
|
|
&& m_node->child1()->child2()->isInt32Constant()
|
|
&& std::abs(m_node->child1()->child2()->asInt32()) <= std::abs(m_node->child2()->asInt32())) {
|
|
convertToIdentityOverChild1();
|
|
}
|
|
break;
|
|
|
|
case ArithDiv:
|
|
// Transform
|
|
// ArithDiv(x, constant)
|
|
// Into
|
|
// ArithMul(x, 1 / constant)
|
|
// if the operation has the same result.
|
|
if (m_node->isBinaryUseKind(DoubleRepUse)
|
|
&& m_node->child2()->isNumberConstant()) {
|
|
|
|
if (Optional<double> reciprocal = safeReciprocalForDivByConst(m_node->child2()->asNumber())) {
|
|
Node* reciprocalNode = m_insertionSet.insertConstant(m_nodeIndex, m_node->origin, jsDoubleNumber(*reciprocal), DoubleConstant);
|
|
m_node->setOp(ArithMul);
|
|
m_node->child2() = Edge(reciprocalNode, DoubleRepUse);
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ValueRep:
|
|
case Int52Rep: {
|
|
// This short-circuits circuitous conversions, like ValueRep(Int52Rep(value)).
|
|
|
|
// The only speculation that we would do beyond validating that we have a type that
|
|
// can be represented a certain way is an Int32 check that would appear on Int52Rep
|
|
// nodes. For now, if we see this and the final type we want is an Int52, we use it
|
|
// as an excuse not to fold. The only thing we would need is a Int52RepInt32Use kind.
|
|
bool hadInt32Check = false;
|
|
if (m_node->op() == Int52Rep) {
|
|
if (m_node->child1().useKind() != Int32Use)
|
|
break;
|
|
hadInt32Check = true;
|
|
}
|
|
for (Node* node = m_node->child1().node(); ; node = node->child1().node()) {
|
|
if (canonicalResultRepresentation(node->result()) ==
|
|
canonicalResultRepresentation(m_node->result())) {
|
|
m_insertionSet.insertCheck(m_graph, m_nodeIndex, m_node);
|
|
if (hadInt32Check) {
|
|
// FIXME: Consider adding Int52RepInt32Use or even DoubleRepInt32Use,
|
|
// which would be super weird. The latter would only arise in some
|
|
// seriously circuitous conversions.
|
|
if (canonicalResultRepresentation(node->result()) != NodeResultJS)
|
|
break;
|
|
|
|
m_insertionSet.insertCheck(
|
|
m_nodeIndex, m_node->origin, Edge(node, Int32Use));
|
|
}
|
|
m_node->child1() = node->defaultEdge();
|
|
m_node->convertToIdentity();
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
|
|
switch (node->op()) {
|
|
case Int52Rep:
|
|
if (node->child1().useKind() != Int32Use)
|
|
break;
|
|
hadInt32Check = true;
|
|
continue;
|
|
|
|
case ValueRep:
|
|
continue;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Flush: {
|
|
ASSERT(m_graph.m_form != SSA);
|
|
|
|
if (m_graph.willCatchExceptionInMachineFrame(m_node->origin.semantic)) {
|
|
// FIXME: We should be able to relax this:
|
|
// https://bugs.webkit.org/show_bug.cgi?id=150824
|
|
break;
|
|
}
|
|
|
|
Node* setLocal = nullptr;
|
|
Operand operand = m_node->operand();
|
|
|
|
for (unsigned i = m_nodeIndex; i--;) {
|
|
Node* node = m_block->at(i);
|
|
|
|
if (node->op() == SetLocal && node->operand() == operand) {
|
|
setLocal = node;
|
|
break;
|
|
}
|
|
|
|
if (accessesOverlap(m_graph, node, AbstractHeap(Stack, operand)))
|
|
break;
|
|
|
|
}
|
|
|
|
if (!setLocal)
|
|
break;
|
|
|
|
// The Flush should become a PhantomLocal at this point. This means that we want the
|
|
// local's value during OSR, but we don't care if the value is stored to the stack. CPS
|
|
// rethreading can canonicalize PhantomLocals for us.
|
|
m_node->convertFlushToPhantomLocal();
|
|
m_graph.dethread();
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
|
|
// FIXME: we should probably do this in constant folding but this currently relies on OSR exit history:
|
|
// https://bugs.webkit.org/show_bug.cgi?id=154832
|
|
case OverridesHasInstance: {
|
|
if (!m_node->child2().node()->isCellConstant())
|
|
break;
|
|
|
|
if (m_node->child2().node()->asCell() != m_graph.globalObjectFor(m_node->origin.semantic)->functionProtoHasInstanceSymbolFunction()) {
|
|
m_graph.convertToConstant(m_node, jsBoolean(true));
|
|
m_changed = true;
|
|
|
|
} else if (!m_graph.hasExitSite(m_node->origin.semantic, BadTypeInfoFlags)) {
|
|
// We optimistically assume that we will not see a function that has a custom instanceof operation as they should be rare.
|
|
m_insertionSet.insertNode(m_nodeIndex, SpecNone, CheckTypeInfoFlags, m_node->origin, OpInfo(ImplementsDefaultHasInstance), Edge(m_node->child1().node(), CellUse));
|
|
m_graph.convertToConstant(m_node, jsBoolean(false));
|
|
m_changed = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// FIXME: We have a lot of string constant-folding rules here. It would be great to
|
|
// move these to the abstract interpreter once AbstractValue can support LazyJSValue.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=155204
|
|
|
|
case ValueAdd: {
|
|
if (m_node->child1()->isConstant()
|
|
&& m_node->child2()->isConstant()
|
|
&& (!!m_node->child1()->tryGetString(m_graph) || !!m_node->child2()->tryGetString(m_graph))) {
|
|
auto tryGetConstantString = [&] (Node* node) -> String {
|
|
String string = node->tryGetString(m_graph);
|
|
if (!string.isEmpty())
|
|
return string;
|
|
JSValue value = node->constant()->value();
|
|
if (!value)
|
|
return String();
|
|
if (value.isInt32())
|
|
return String::number(value.asInt32());
|
|
if (value.isNumber())
|
|
return String::number(value.asNumber());
|
|
if (value.isBoolean())
|
|
return value.asBoolean() ? "true"_s : "false"_s;
|
|
if (value.isNull())
|
|
return "null"_s;
|
|
if (value.isUndefined())
|
|
return "undefined"_s;
|
|
return String();
|
|
};
|
|
|
|
String leftString = tryGetConstantString(m_node->child1().node());
|
|
if (!leftString)
|
|
break;
|
|
String rightString = tryGetConstantString(m_node->child2().node());
|
|
if (!rightString)
|
|
break;
|
|
|
|
StringBuilder builder;
|
|
builder.append(leftString);
|
|
builder.append(rightString);
|
|
convertToLazyJSValue(m_node, LazyJSValue::newString(m_graph, builder.toString()));
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
|
|
if (m_node->binaryUseKind() == BigInt32Use || m_node->binaryUseKind() == HeapBigIntUse || m_node->binaryUseKind() == AnyBigIntUse)
|
|
handleCommutativity();
|
|
|
|
break;
|
|
}
|
|
|
|
case MakeRope:
|
|
case StrCat: {
|
|
String leftString = m_node->child1()->tryGetString(m_graph);
|
|
if (!leftString)
|
|
break;
|
|
String rightString = m_node->child2()->tryGetString(m_graph);
|
|
if (!rightString)
|
|
break;
|
|
String extraString;
|
|
if (m_node->child3()) {
|
|
extraString = m_node->child3()->tryGetString(m_graph);
|
|
if (!extraString)
|
|
break;
|
|
}
|
|
|
|
StringBuilder builder;
|
|
builder.append(leftString);
|
|
builder.append(rightString);
|
|
if (!!extraString)
|
|
builder.append(extraString);
|
|
|
|
convertToLazyJSValue(m_node, LazyJSValue::newString(m_graph, builder.toString()));
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
|
|
case ToString:
|
|
case CallStringConstructor: {
|
|
Edge& child1 = m_node->child1();
|
|
switch (child1.useKind()) {
|
|
case Int32Use:
|
|
case Int52RepUse:
|
|
case DoubleRepUse: {
|
|
if (child1->hasConstant()) {
|
|
JSValue value = child1->constant()->value();
|
|
if (value) {
|
|
String result;
|
|
if (value.isInt32())
|
|
result = String::number(value.asInt32());
|
|
else if (value.isNumber())
|
|
result = String::number(value.asNumber());
|
|
|
|
if (!result.isNull()) {
|
|
convertToLazyJSValue(m_node, LazyJSValue::newString(m_graph, result));
|
|
m_changed = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NumberToStringWithValidRadixConstant: {
|
|
Edge& child1 = m_node->child1();
|
|
if (child1->hasConstant()) {
|
|
JSValue value = child1->constant()->value();
|
|
if (value && value.isNumber()) {
|
|
String result = toStringWithRadix(value.asNumber(), m_node->validRadixConstant());
|
|
convertToLazyJSValue(m_node, LazyJSValue::newString(m_graph, result));
|
|
m_changed = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GetArrayLength: {
|
|
if (m_node->arrayMode().type() == Array::Generic
|
|
|| m_node->arrayMode().type() == Array::String) {
|
|
String string = m_node->child1()->tryGetString(m_graph);
|
|
if (!!string) {
|
|
m_graph.convertToConstant(m_node, jsNumber(string.length()));
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GetGlobalObject: {
|
|
if (JSObject* object = m_node->child1()->dynamicCastConstant<JSObject*>(vm())) {
|
|
m_graph.convertToConstant(m_node, object->globalObject(vm()));
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RegExpExec:
|
|
case RegExpTest:
|
|
case RegExpMatchFast:
|
|
case RegExpExecNonGlobalOrSticky: {
|
|
JSGlobalObject* globalObject = m_node->child1()->dynamicCastConstant<JSGlobalObject*>(vm());
|
|
if (!globalObject) {
|
|
if (verbose)
|
|
dataLog("Giving up because no global object.\n");
|
|
break;
|
|
}
|
|
|
|
if (globalObject->isHavingABadTime()) {
|
|
if (verbose)
|
|
dataLog("Giving up because bad time.\n");
|
|
break;
|
|
}
|
|
|
|
Node* regExpObjectNode = nullptr;
|
|
RegExp* regExp = nullptr;
|
|
if (m_node->op() == RegExpExec || m_node->op() == RegExpTest || m_node->op() == RegExpMatchFast) {
|
|
regExpObjectNode = m_node->child2().node();
|
|
if (RegExpObject* regExpObject = regExpObjectNode->dynamicCastConstant<RegExpObject*>(vm()))
|
|
regExp = regExpObject->regExp();
|
|
else if (regExpObjectNode->op() == NewRegexp)
|
|
regExp = regExpObjectNode->castOperand<RegExp*>();
|
|
else {
|
|
if (verbose)
|
|
dataLog("Giving up because the regexp is unknown.\n");
|
|
break;
|
|
}
|
|
} else
|
|
regExp = m_node->castOperand<RegExp*>();
|
|
|
|
if (m_node->op() == RegExpMatchFast) {
|
|
if (regExp->global()) {
|
|
if (regExp->sticky())
|
|
break;
|
|
if (m_node->child3().useKind() != StringUse)
|
|
break;
|
|
NodeOrigin origin = m_node->origin;
|
|
m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, Check, origin, m_node->children.justChecks());
|
|
m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, SetRegExpObjectLastIndex, origin,
|
|
OpInfo(false),
|
|
Edge(regExpObjectNode, RegExpObjectUse),
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsNumber(0), UntypedUse));
|
|
origin = origin.withInvalidExit();
|
|
m_node->convertToRegExpMatchFastGlobalWithoutChecks(m_graph.freeze(regExp));
|
|
m_node->origin = origin;
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
|
|
m_node->setOp(RegExpExec);
|
|
m_changed = true;
|
|
// Continue performing strength reduction onto RegExpExec node.
|
|
}
|
|
|
|
ASSERT(m_node->op() != RegExpMatchFast);
|
|
|
|
unsigned lastIndex = UINT_MAX;
|
|
if (m_node->op() != RegExpExecNonGlobalOrSticky) {
|
|
// This will only work if we can prove what the value of lastIndex is. To do this
|
|
// safely, we need to execute the insertion set so that we see any previous strength
|
|
// reductions. This is needed for soundness since otherwise the effectfulness of any
|
|
// previous strength reductions would be invisible to us.
|
|
ASSERT(regExpObjectNode);
|
|
executeInsertionSet();
|
|
for (unsigned otherNodeIndex = m_nodeIndex; otherNodeIndex--;) {
|
|
Node* otherNode = m_block->at(otherNodeIndex);
|
|
if (otherNode == regExpObjectNode) {
|
|
lastIndex = 0;
|
|
break;
|
|
}
|
|
if (otherNode->op() == SetRegExpObjectLastIndex
|
|
&& otherNode->child1() == regExpObjectNode
|
|
&& otherNode->child2()->isInt32Constant()
|
|
&& otherNode->child2()->asInt32() >= 0) {
|
|
lastIndex = otherNode->child2()->asUInt32();
|
|
break;
|
|
}
|
|
if (writesOverlap(m_graph, otherNode, RegExpObject_lastIndex))
|
|
break;
|
|
}
|
|
if (lastIndex == UINT_MAX) {
|
|
if (verbose)
|
|
dataLog("Giving up because the last index is not known.\n");
|
|
break;
|
|
}
|
|
}
|
|
if (!regExp->globalOrSticky())
|
|
lastIndex = 0;
|
|
|
|
auto foldToConstant = [&] {
|
|
Node* stringNode = nullptr;
|
|
if (m_node->op() == RegExpExecNonGlobalOrSticky)
|
|
stringNode = m_node->child2().node();
|
|
else
|
|
stringNode = m_node->child3().node();
|
|
|
|
// NOTE: This mostly already protects us from having the compiler execute a regexp
|
|
// operation on a ginormous string by preventing us from getting our hands on ginormous
|
|
// strings in the first place.
|
|
String string = stringNode->tryGetString(m_graph);
|
|
if (!string) {
|
|
if (verbose)
|
|
dataLog("Giving up because the string is unknown.\n");
|
|
return false;
|
|
}
|
|
|
|
FrozenValue* regExpFrozenValue = m_graph.freeze(regExp);
|
|
|
|
// Refuse to do things with regular expressions that have a ginormous number of
|
|
// subpatterns.
|
|
unsigned ginormousNumberOfSubPatterns = 1000;
|
|
if (regExp->numSubpatterns() > ginormousNumberOfSubPatterns) {
|
|
if (verbose)
|
|
dataLog("Giving up because of pattern limit.\n");
|
|
return false;
|
|
}
|
|
|
|
if ((m_node->op() == RegExpExec || m_node->op() == RegExpExecNonGlobalOrSticky) && regExp->hasNamedCaptures()) {
|
|
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=176464
|
|
// Implement strength reduction optimization for named capture groups.
|
|
if (verbose)
|
|
dataLog("Giving up because of named capture groups.\n");
|
|
return false;
|
|
}
|
|
|
|
m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint());
|
|
|
|
Structure* structure = globalObject->regExpMatchesArrayStructure();
|
|
if (structure->indexingType() != ArrayWithContiguous) {
|
|
// This is further protection against a race with haveABadTime.
|
|
if (verbose)
|
|
dataLog("Giving up because the structure has the wrong indexing type.\n");
|
|
return false;
|
|
}
|
|
m_graph.registerStructure(structure);
|
|
|
|
FrozenValue* globalObjectFrozenValue = m_graph.freeze(globalObject);
|
|
|
|
MatchResult result;
|
|
Vector<int> ovector;
|
|
// We have to call the kind of match function that the main thread would have called.
|
|
// Otherwise, we might not have the desired Yarr code compiled, and the match will fail.
|
|
if (m_node->op() == RegExpExec || m_node->op() == RegExpExecNonGlobalOrSticky) {
|
|
int position;
|
|
if (!regExp->matchConcurrently(vm(), string, lastIndex, position, ovector)) {
|
|
if (verbose)
|
|
dataLog("Giving up because match failed.\n");
|
|
return false;
|
|
}
|
|
result.start = position;
|
|
result.end = ovector[1];
|
|
} else {
|
|
if (!regExp->matchConcurrently(vm(), string, lastIndex, result)) {
|
|
if (verbose)
|
|
dataLog("Giving up because match failed.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We've constant-folded the regexp. Now we're committed to replacing RegExpExec/Test.
|
|
|
|
m_changed = true;
|
|
|
|
NodeOrigin origin = m_node->origin;
|
|
|
|
m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, Check, origin, m_node->children.justChecks());
|
|
|
|
if (m_node->op() == RegExpExec || m_node->op() == RegExpExecNonGlobalOrSticky) {
|
|
if (result) {
|
|
RegisteredStructureSet* structureSet = m_graph.addStructureSet(structure);
|
|
|
|
// Create an array modeling the JS array that we will try to allocate. This is
|
|
// basically createRegExpMatchesArray but over C++ strings instead of JSStrings.
|
|
Vector<String> resultArray;
|
|
resultArray.append(string.substring(result.start, result.end - result.start));
|
|
for (unsigned i = 1; i <= regExp->numSubpatterns(); ++i) {
|
|
int start = ovector[2 * i];
|
|
if (start >= 0)
|
|
resultArray.append(string.substring(start, ovector[2 * i + 1] - start));
|
|
else
|
|
resultArray.append(String());
|
|
}
|
|
|
|
unsigned publicLength = resultArray.size();
|
|
unsigned vectorLength =
|
|
Butterfly::optimalContiguousVectorLength(structure, publicLength);
|
|
|
|
UniquedStringImpl* indexUID = vm().propertyNames->index.impl();
|
|
UniquedStringImpl* inputUID = vm().propertyNames->input.impl();
|
|
UniquedStringImpl* groupsUID = vm().propertyNames->groups.impl();
|
|
unsigned indexIndex = m_graph.identifiers().ensure(indexUID);
|
|
unsigned inputIndex = m_graph.identifiers().ensure(inputUID);
|
|
unsigned groupsIndex = m_graph.identifiers().ensure(groupsUID);
|
|
|
|
unsigned firstChild = m_graph.m_varArgChildren.size();
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, structure, KnownCellUse));
|
|
ObjectMaterializationData* data = m_graph.m_objectMaterializationData.add();
|
|
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsNumber(publicLength), KnownInt32Use));
|
|
data->m_properties.append(PublicLengthPLoc);
|
|
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsNumber(vectorLength), KnownInt32Use));
|
|
data->m_properties.append(VectorLengthPLoc);
|
|
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsNumber(result.start), UntypedUse));
|
|
data->m_properties.append(
|
|
PromotedLocationDescriptor(NamedPropertyPLoc, indexIndex));
|
|
|
|
m_graph.m_varArgChildren.append(Edge(stringNode, UntypedUse));
|
|
data->m_properties.append(
|
|
PromotedLocationDescriptor(NamedPropertyPLoc, inputIndex));
|
|
|
|
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=176464
|
|
// Implement strength reduction optimization for named capture groups.
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsUndefined(), UntypedUse));
|
|
data->m_properties.append(
|
|
PromotedLocationDescriptor(NamedPropertyPLoc, groupsIndex));
|
|
|
|
auto materializeString = [&] (const String& string) -> Node* {
|
|
if (string.isNull())
|
|
return nullptr;
|
|
if (string.isEmpty()) {
|
|
return m_insertionSet.insertConstant(
|
|
m_nodeIndex, origin, vm().smallStrings.emptyString());
|
|
}
|
|
LazyJSValue value = LazyJSValue::newString(m_graph, string);
|
|
return m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, LazyJSConstant, origin,
|
|
OpInfo(m_graph.m_lazyJSValues.add(value)));
|
|
};
|
|
|
|
for (unsigned i = 0; i < resultArray.size(); ++i) {
|
|
if (Node* node = materializeString(resultArray[i])) {
|
|
m_graph.m_varArgChildren.append(Edge(node, UntypedUse));
|
|
data->m_properties.append(
|
|
PromotedLocationDescriptor(IndexedPropertyPLoc, i));
|
|
}
|
|
}
|
|
|
|
Node* resultNode = m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecArray, Node::VarArg, MaterializeNewObject, origin,
|
|
OpInfo(structureSet), OpInfo(data), firstChild,
|
|
m_graph.m_varArgChildren.size() - firstChild);
|
|
|
|
m_node->convertToIdentityOn(resultNode);
|
|
} else
|
|
m_graph.convertToConstant(m_node, jsNull());
|
|
} else
|
|
m_graph.convertToConstant(m_node, jsBoolean(!!result));
|
|
|
|
// Whether it's Exec or Test, we need to tell the globalObject and RegExpObject what's up.
|
|
// Because SetRegExpObjectLastIndex may exit and it clobbers exit state, we do that
|
|
// first.
|
|
|
|
if (regExp->globalOrSticky()) {
|
|
ASSERT(regExpObjectNode);
|
|
m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, SetRegExpObjectLastIndex, origin,
|
|
OpInfo(false),
|
|
Edge(regExpObjectNode, RegExpObjectUse),
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsNumber(result ? result.end : 0), UntypedUse));
|
|
|
|
origin = origin.withInvalidExit();
|
|
}
|
|
|
|
if (result) {
|
|
unsigned firstChild = m_graph.m_varArgChildren.size();
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, globalObjectFrozenValue, KnownCellUse));
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, regExpFrozenValue, KnownCellUse));
|
|
m_graph.m_varArgChildren.append(Edge(stringNode, KnownCellUse));
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsNumber(result.start), KnownInt32Use));
|
|
m_graph.m_varArgChildren.append(
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsNumber(result.end), KnownInt32Use));
|
|
m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, Node::VarArg, RecordRegExpCachedResult, origin,
|
|
OpInfo(), OpInfo(), firstChild, m_graph.m_varArgChildren.size() - firstChild);
|
|
|
|
origin = origin.withInvalidExit();
|
|
}
|
|
|
|
m_node->origin = origin;
|
|
return true;
|
|
};
|
|
|
|
auto convertToStatic = [&] {
|
|
if (m_node->op() != RegExpExec)
|
|
return false;
|
|
if (regExp->globalOrSticky())
|
|
return false;
|
|
if (m_node->child3().useKind() != StringUse)
|
|
return false;
|
|
NodeOrigin origin = m_node->origin;
|
|
m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, Check, origin, m_node->children.justChecks());
|
|
m_node->convertToRegExpExecNonGlobalOrStickyWithoutChecks(m_graph.freeze(regExp));
|
|
m_changed = true;
|
|
return true;
|
|
};
|
|
|
|
if (foldToConstant())
|
|
break;
|
|
|
|
if (convertToStatic())
|
|
break;
|
|
|
|
break;
|
|
}
|
|
|
|
case StringReplace:
|
|
case StringReplaceRegExp: {
|
|
Node* stringNode = m_node->child1().node();
|
|
String string = stringNode->tryGetString(m_graph);
|
|
if (!string)
|
|
break;
|
|
|
|
Node* regExpObjectNode = m_node->child2().node();
|
|
RegExp* regExp;
|
|
if (RegExpObject* regExpObject = regExpObjectNode->dynamicCastConstant<RegExpObject*>(vm()))
|
|
regExp = regExpObject->regExp();
|
|
else if (regExpObjectNode->op() == NewRegexp)
|
|
regExp = regExpObjectNode->castOperand<RegExp*>();
|
|
else {
|
|
if (verbose)
|
|
dataLog("Giving up because the regexp is unknown.\n");
|
|
break;
|
|
}
|
|
|
|
String replace = m_node->child3()->tryGetString(m_graph);
|
|
if (!replace)
|
|
break;
|
|
|
|
StringBuilder builder;
|
|
|
|
unsigned lastIndex = 0;
|
|
unsigned startPosition = 0;
|
|
bool ok = true;
|
|
do {
|
|
MatchResult result;
|
|
Vector<int> ovector;
|
|
// Model which version of match() is called by the main thread.
|
|
if (replace.isEmpty() && regExp->global()) {
|
|
if (!regExp->matchConcurrently(vm(), string, startPosition, result)) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
} else {
|
|
int position;
|
|
if (!regExp->matchConcurrently(vm(), string, startPosition, position, ovector)) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
|
|
result.start = position;
|
|
result.end = ovector[1];
|
|
}
|
|
|
|
if (!result)
|
|
break;
|
|
|
|
unsigned replLen = replace.length();
|
|
if (lastIndex < result.start || replLen) {
|
|
builder.appendSubstring(string, lastIndex, result.start - lastIndex);
|
|
if (replLen) {
|
|
StringBuilder replacement;
|
|
substituteBackreferences(replacement, replace, string, ovector.data(), regExp);
|
|
builder.append(replacement);
|
|
}
|
|
}
|
|
|
|
lastIndex = result.end;
|
|
startPosition = lastIndex;
|
|
|
|
// special case of empty match
|
|
if (result.empty()) {
|
|
startPosition++;
|
|
if (startPosition > string.length())
|
|
break;
|
|
}
|
|
} while (regExp->global());
|
|
if (!ok)
|
|
break;
|
|
|
|
// We are committed at this point.
|
|
m_changed = true;
|
|
|
|
NodeOrigin origin = m_node->origin;
|
|
|
|
// Preserve any checks we have.
|
|
m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, Check, origin, m_node->children.justChecks());
|
|
|
|
if (regExp->global()) {
|
|
m_insertionSet.insertNode(
|
|
m_nodeIndex, SpecNone, SetRegExpObjectLastIndex, origin,
|
|
OpInfo(false),
|
|
Edge(regExpObjectNode, RegExpObjectUse),
|
|
m_insertionSet.insertConstantForUse(
|
|
m_nodeIndex, origin, jsNumber(0), UntypedUse));
|
|
|
|
origin = origin.withInvalidExit();
|
|
}
|
|
|
|
if (!lastIndex && builder.isEmpty())
|
|
m_node->convertToIdentityOn(stringNode);
|
|
else {
|
|
builder.appendSubstring(string, lastIndex);
|
|
m_node->convertToLazyJSConstant(m_graph, LazyJSValue::newString(m_graph, builder.toString()));
|
|
}
|
|
|
|
m_node->origin = origin;
|
|
break;
|
|
}
|
|
|
|
case Call:
|
|
case Construct:
|
|
case TailCallInlinedCaller:
|
|
case TailCall: {
|
|
ExecutableBase* executable = nullptr;
|
|
Edge callee = m_graph.varArgChild(m_node, 0);
|
|
CallVariant callVariant;
|
|
if (JSFunction* function = callee->dynamicCastConstant<JSFunction*>(vm())) {
|
|
executable = function->executable();
|
|
callVariant = CallVariant(function);
|
|
} else if (callee->isFunctionAllocation()) {
|
|
executable = callee->castOperand<FunctionExecutable*>();
|
|
callVariant = CallVariant(executable);
|
|
}
|
|
|
|
if (!executable)
|
|
break;
|
|
|
|
// FIXME: Support wasm IC.
|
|
// DirectCall to wasm function has suboptimal implementation. We avoid using DirectCall if we know that function is a wasm function.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=220339
|
|
if (executable->intrinsic() == WasmFunctionIntrinsic)
|
|
break;
|
|
|
|
if (FunctionExecutable* functionExecutable = jsDynamicCast<FunctionExecutable*>(vm(), executable)) {
|
|
if (m_node->op() == Construct && functionExecutable->constructAbility() == ConstructAbility::CannotConstruct)
|
|
break;
|
|
|
|
// We need to update m_parameterSlots before we get to the backend, but we don't
|
|
// want to do too much of this.
|
|
unsigned numAllocatedArgs =
|
|
static_cast<unsigned>(functionExecutable->parameterCount()) + 1;
|
|
|
|
if (numAllocatedArgs <= Options::maximumDirectCallStackSize()) {
|
|
m_graph.m_parameterSlots = std::max(
|
|
m_graph.m_parameterSlots,
|
|
Graph::parameterSlotsForArgCount(numAllocatedArgs));
|
|
}
|
|
}
|
|
|
|
m_graph.m_plan.recordedStatuses().addCallLinkStatus(m_node->origin.semantic, CallLinkStatus(callVariant));
|
|
|
|
m_node->convertToDirectCall(m_graph.freeze(executable));
|
|
m_changed = true;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void convertToIdentityOverChild(unsigned childIndex)
|
|
{
|
|
ASSERT(!(m_node->flags() & NodeHasVarArgs));
|
|
m_insertionSet.insertCheck(m_graph, m_nodeIndex, m_node);
|
|
m_node->children.removeEdge(childIndex ^ 1);
|
|
m_node->convertToIdentity();
|
|
m_changed = true;
|
|
}
|
|
|
|
void convertToIdentityOverChild1()
|
|
{
|
|
convertToIdentityOverChild(0);
|
|
}
|
|
|
|
void convertToIdentityOverChild2()
|
|
{
|
|
convertToIdentityOverChild(1);
|
|
}
|
|
|
|
void convertToLazyJSValue(Node* node, LazyJSValue value)
|
|
{
|
|
m_insertionSet.insertCheck(m_graph, m_nodeIndex, node);
|
|
node->convertToLazyJSConstant(m_graph, value);
|
|
}
|
|
|
|
void handleCommutativity()
|
|
{
|
|
// It's definitely not sound to swap the lhs and rhs when we may be performing effectful
|
|
// calls on the lhs/rhs for valueOf.
|
|
if (m_node->child1().useKind() == UntypedUse || m_node->child2().useKind() == UntypedUse)
|
|
return;
|
|
|
|
// If the right side is a constant then there is nothing left to do.
|
|
if (m_node->child2()->hasConstant())
|
|
return;
|
|
|
|
// This case ensures that optimizations that look for x + const don't also have
|
|
// to look for const + x.
|
|
if (m_node->child1()->hasConstant() && !m_node->child1()->asJSValue().isCell()) {
|
|
std::swap(m_node->child1(), m_node->child2());
|
|
m_changed = true;
|
|
return;
|
|
}
|
|
|
|
// This case ensures that CSE is commutativity-aware.
|
|
if (m_node->child1().node() > m_node->child2().node()) {
|
|
std::swap(m_node->child1(), m_node->child2());
|
|
m_changed = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void executeInsertionSet()
|
|
{
|
|
m_nodeIndex += m_insertionSet.execute(m_block);
|
|
}
|
|
|
|
InsertionSet m_insertionSet;
|
|
BasicBlock* m_block;
|
|
unsigned m_nodeIndex;
|
|
Node* m_node;
|
|
bool m_changed;
|
|
};
|
|
|
|
bool performStrengthReduction(Graph& graph)
|
|
{
|
|
return runPhase<StrengthReductionPhase>(graph);
|
|
}
|
|
|
|
} } // namespace JSC::DFG
|
|
|
|
#endif // ENABLE(DFG_JIT)
|
|
|