blob: f33a2fb66c2bf5585c3a6cba6cc24d3728998059 [file] [log] [blame]
/*
* Copyright (C) 2012, 2013, 2014 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"
#if ENABLE(DFG_JIT)
#include "DFGFixupPhase.h"
#include "DFGGraph.h"
#include "DFGInsertionSet.h"
#include "DFGPhase.h"
#include "DFGPredictionPropagationPhase.h"
#include "DFGVariableAccessDataDump.h"
#include "JSCInlines.h"
namespace JSC { namespace DFG {
class FixupPhase : public Phase {
public:
FixupPhase(Graph& graph)
: Phase(graph, "fixup")
, m_insertionSet(graph)
{
}
bool run()
{
ASSERT(m_graph.m_fixpointState == BeforeFixpoint);
ASSERT(m_graph.m_form == ThreadedCPS);
m_profitabilityChanged = false;
for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex)
fixupBlock(m_graph.block(blockIndex));
while (m_profitabilityChanged) {
m_profitabilityChanged = false;
for (unsigned i = m_graph.m_argumentPositions.size(); i--;)
m_graph.m_argumentPositions[i].mergeArgumentUnboxingAwareness();
for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex)
fixupSetLocalsInBlock(m_graph.block(blockIndex));
}
for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex)
fixupUntypedSetLocalsInBlock(m_graph.block(blockIndex));
return true;
}
private:
void fixupBlock(BasicBlock* block)
{
if (!block)
return;
ASSERT(block->isReachable);
m_block = block;
for (m_indexInBlock = 0; m_indexInBlock < block->size(); ++m_indexInBlock) {
m_currentNode = block->at(m_indexInBlock);
fixupNode(m_currentNode);
}
m_insertionSet.execute(block);
}
void fixupNode(Node* node)
{
NodeType op = node->op();
switch (op) {
case SetLocal: {
// This gets handled by fixupSetLocalsInBlock().
return;
}
case BitAnd:
case BitOr:
case BitXor:
case BitRShift:
case BitLShift:
case BitURShift: {
fixIntEdge(node->child1());
fixIntEdge(node->child2());
break;
}
case ArithIMul: {
fixIntEdge(node->child1());
fixIntEdge(node->child2());
node->setOp(ArithMul);
node->setArithMode(Arith::Unchecked);
node->child1().setUseKind(Int32Use);
node->child2().setUseKind(Int32Use);
break;
}
case UInt32ToNumber: {
fixIntEdge(node->child1());
if (bytecodeCanTruncateInteger(node->arithNodeFlags()))
node->convertToIdentity();
else if (nodeCanSpeculateInt32(node->arithNodeFlags()))
node->setArithMode(Arith::CheckOverflow);
else
node->setArithMode(Arith::DoOverflow);
break;
}
case ValueAdd: {
if (attemptToMakeIntegerAdd(node)) {
node->setOp(ArithAdd);
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (Node::shouldSpeculateNumberExpectingDefined(node->child1().node(), node->child2().node())) {
fixEdge<NumberUse>(node->child1());
fixEdge<NumberUse>(node->child2());
node->setOp(ArithAdd);
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
// FIXME: Optimize for the case where one of the operands is the
// empty string. Also consider optimizing for the case where we don't
// believe either side is the emtpy string. Both of these things should
// be easy.
if (node->child1()->shouldSpeculateString()
&& attemptToMakeFastStringAdd<StringUse>(node, node->child1(), node->child2()))
break;
if (node->child2()->shouldSpeculateString()
&& attemptToMakeFastStringAdd<StringUse>(node, node->child2(), node->child1()))
break;
if (node->child1()->shouldSpeculateStringObject()
&& attemptToMakeFastStringAdd<StringObjectUse>(node, node->child1(), node->child2()))
break;
if (node->child2()->shouldSpeculateStringObject()
&& attemptToMakeFastStringAdd<StringObjectUse>(node, node->child2(), node->child1()))
break;
if (node->child1()->shouldSpeculateStringOrStringObject()
&& attemptToMakeFastStringAdd<StringOrStringObjectUse>(node, node->child1(), node->child2()))
break;
if (node->child2()->shouldSpeculateStringOrStringObject()
&& attemptToMakeFastStringAdd<StringOrStringObjectUse>(node, node->child2(), node->child1()))
break;
break;
}
case MakeRope: {
fixupMakeRope(node);
break;
}
case ArithAdd:
case ArithSub: {
if (attemptToMakeIntegerAdd(node))
break;
fixEdge<NumberUse>(node->child1());
fixEdge<NumberUse>(node->child2());
break;
}
case ArithNegate: {
if (m_graph.negateShouldSpeculateInt32(node)) {
fixEdge<Int32Use>(node->child1());
if (bytecodeCanTruncateInteger(node->arithNodeFlags()))
node->setArithMode(Arith::Unchecked);
else if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
node->setArithMode(Arith::CheckOverflow);
else
node->setArithMode(Arith::CheckOverflowAndNegativeZero);
break;
}
if (m_graph.negateShouldSpeculateMachineInt(node)) {
fixEdge<MachineIntUse>(node->child1());
if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
node->setArithMode(Arith::CheckOverflow);
else
node->setArithMode(Arith::CheckOverflowAndNegativeZero);
break;
}
fixEdge<NumberUse>(node->child1());
break;
}
case ArithMul: {
if (m_graph.mulShouldSpeculateInt32(node)) {
fixEdge<Int32Use>(node->child1());
fixEdge<Int32Use>(node->child2());
if (bytecodeCanTruncateInteger(node->arithNodeFlags()))
node->setArithMode(Arith::Unchecked);
else if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
node->setArithMode(Arith::CheckOverflow);
else
node->setArithMode(Arith::CheckOverflowAndNegativeZero);
break;
}
if (m_graph.mulShouldSpeculateMachineInt(node)) {
fixEdge<MachineIntUse>(node->child1());
fixEdge<MachineIntUse>(node->child2());
if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
node->setArithMode(Arith::CheckOverflow);
else
node->setArithMode(Arith::CheckOverflowAndNegativeZero);
break;
}
fixEdge<NumberUse>(node->child1());
fixEdge<NumberUse>(node->child2());
break;
}
case ArithDiv:
case ArithMod: {
if (Node::shouldSpeculateInt32ForArithmetic(node->child1().node(), node->child2().node())
&& node->canSpeculateInt32()) {
if (optimizeForX86() || optimizeForARM64() || optimizeForARMv7s()) {
fixEdge<Int32Use>(node->child1());
fixEdge<Int32Use>(node->child2());
if (bytecodeCanTruncateInteger(node->arithNodeFlags()))
node->setArithMode(Arith::Unchecked);
else if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
node->setArithMode(Arith::CheckOverflow);
else
node->setArithMode(Arith::CheckOverflowAndNegativeZero);
break;
}
Edge child1 = node->child1();
Edge child2 = node->child2();
injectInt32ToDoubleNode(node->child1());
injectInt32ToDoubleNode(node->child2());
// We don't need to do ref'ing on the children because we're stealing them from
// the original division.
Node* newDivision = m_insertionSet.insertNode(
m_indexInBlock, SpecDouble, *node);
node->setOp(DoubleAsInt32);
node->children.initialize(Edge(newDivision, KnownNumberUse), Edge(), Edge());
if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
node->setArithMode(Arith::CheckOverflow);
else
node->setArithMode(Arith::CheckOverflowAndNegativeZero);
m_insertionSet.insertNode(m_indexInBlock + 1, SpecNone, Phantom, node->codeOrigin, child1, child2);
break;
}
fixEdge<NumberUse>(node->child1());
fixEdge<NumberUse>(node->child2());
break;
}
case ArithMin:
case ArithMax: {
if (Node::shouldSpeculateInt32ForArithmetic(node->child1().node(), node->child2().node())
&& node->canSpeculateInt32()) {
fixEdge<Int32Use>(node->child1());
fixEdge<Int32Use>(node->child2());
break;
}
fixEdge<NumberUse>(node->child1());
fixEdge<NumberUse>(node->child2());
break;
}
case ArithAbs: {
if (node->child1()->shouldSpeculateInt32ForArithmetic()
&& node->canSpeculateInt32()) {
fixEdge<Int32Use>(node->child1());
break;
}
fixEdge<NumberUse>(node->child1());
break;
}
case ArithSqrt:
case ArithSin:
case ArithCos: {
fixEdge<NumberUse>(node->child1());
break;
}
case LogicalNot: {
if (node->child1()->shouldSpeculateBoolean())
fixEdge<BooleanUse>(node->child1());
else if (node->child1()->shouldSpeculateObjectOrOther())
fixEdge<ObjectOrOtherUse>(node->child1());
else if (node->child1()->shouldSpeculateInt32())
fixEdge<Int32Use>(node->child1());
else if (node->child1()->shouldSpeculateNumber())
fixEdge<NumberUse>(node->child1());
else if (node->child1()->shouldSpeculateString())
fixEdge<StringUse>(node->child1());
break;
}
case TypeOf: {
if (node->child1()->shouldSpeculateString())
fixEdge<StringUse>(node->child1());
else if (node->child1()->shouldSpeculateCell())
fixEdge<CellUse>(node->child1());
break;
}
case CompareEqConstant: {
break;
}
case CompareEq:
case CompareLess:
case CompareLessEq:
case CompareGreater:
case CompareGreaterEq: {
if (Node::shouldSpeculateInt32(node->child1().node(), node->child2().node())) {
fixEdge<Int32Use>(node->child1());
fixEdge<Int32Use>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (enableInt52()
&& Node::shouldSpeculateMachineInt(node->child1().node(), node->child2().node())) {
fixEdge<MachineIntUse>(node->child1());
fixEdge<MachineIntUse>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (Node::shouldSpeculateNumber(node->child1().node(), node->child2().node())) {
fixEdge<NumberUse>(node->child1());
fixEdge<NumberUse>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (node->op() != CompareEq)
break;
if (Node::shouldSpeculateBoolean(node->child1().node(), node->child2().node())) {
fixEdge<BooleanUse>(node->child1());
fixEdge<BooleanUse>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (node->child1()->shouldSpeculateStringIdent() && node->child2()->shouldSpeculateStringIdent()) {
fixEdge<StringIdentUse>(node->child1());
fixEdge<StringIdentUse>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (node->child1()->shouldSpeculateString() && node->child2()->shouldSpeculateString() && GPRInfo::numberOfRegisters >= 7) {
fixEdge<StringUse>(node->child1());
fixEdge<StringUse>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (node->child1()->shouldSpeculateObject() && node->child2()->shouldSpeculateObject()) {
fixEdge<ObjectUse>(node->child1());
fixEdge<ObjectUse>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (node->child1()->shouldSpeculateObject() && node->child2()->shouldSpeculateObjectOrOther()) {
fixEdge<ObjectUse>(node->child1());
fixEdge<ObjectOrOtherUse>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
if (node->child1()->shouldSpeculateObjectOrOther() && node->child2()->shouldSpeculateObject()) {
fixEdge<ObjectOrOtherUse>(node->child1());
fixEdge<ObjectUse>(node->child2());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
break;
}
case CompareStrictEqConstant: {
break;
}
case CompareStrictEq: {
if (Node::shouldSpeculateBoolean(node->child1().node(), node->child2().node())) {
fixEdge<BooleanUse>(node->child1());
fixEdge<BooleanUse>(node->child2());
break;
}
if (Node::shouldSpeculateInt32(node->child1().node(), node->child2().node())) {
fixEdge<Int32Use>(node->child1());
fixEdge<Int32Use>(node->child2());
break;
}
if (enableInt52()
&& Node::shouldSpeculateMachineInt(node->child1().node(), node->child2().node())) {
fixEdge<MachineIntUse>(node->child1());
fixEdge<MachineIntUse>(node->child2());
break;
}
if (Node::shouldSpeculateNumber(node->child1().node(), node->child2().node())) {
fixEdge<NumberUse>(node->child1());
fixEdge<NumberUse>(node->child2());
break;
}
if (node->child1()->shouldSpeculateStringIdent() && node->child2()->shouldSpeculateStringIdent()) {
fixEdge<StringIdentUse>(node->child1());
fixEdge<StringIdentUse>(node->child2());
break;
}
if (node->child1()->shouldSpeculateString() && node->child2()->shouldSpeculateString() && GPRInfo::numberOfRegisters >= 7) {
fixEdge<StringUse>(node->child1());
fixEdge<StringUse>(node->child2());
break;
}
if (node->child1()->shouldSpeculateObject() && node->child2()->shouldSpeculateObject()) {
fixEdge<ObjectUse>(node->child1());
fixEdge<ObjectUse>(node->child2());
break;
}
break;
}
case StringFromCharCode:
fixEdge<Int32Use>(node->child1());
break;
case StringCharAt:
case StringCharCodeAt: {
// Currently we have no good way of refining these.
ASSERT(node->arrayMode() == ArrayMode(Array::String));
blessArrayOperation(node->child1(), node->child2(), node->child3());
fixEdge<KnownCellUse>(node->child1());
fixEdge<Int32Use>(node->child2());
break;
}
case GetByVal: {
node->setArrayMode(
node->arrayMode().refine(
m_graph, node->codeOrigin,
node->child1()->prediction(),
node->child2()->prediction(),
SpecNone, node->flags()));
blessArrayOperation(node->child1(), node->child2(), node->child3());
ArrayMode arrayMode = node->arrayMode();
switch (arrayMode.type()) {
case Array::Double:
if (arrayMode.arrayClass() == Array::OriginalArray
&& arrayMode.speculation() == Array::InBounds
&& m_graph.globalObjectFor(node->codeOrigin)->arrayPrototypeChainIsSane()
&& !(node->flags() & NodeBytecodeUsesAsOther))
node->setArrayMode(arrayMode.withSpeculation(Array::SaneChain));
break;
case Array::String:
if ((node->prediction() & ~SpecString)
|| m_graph.hasExitSite(node->codeOrigin, OutOfBounds))
node->setArrayMode(arrayMode.withSpeculation(Array::OutOfBounds));
break;
default:
break;
}
switch (node->arrayMode().type()) {
case Array::SelectUsingPredictions:
case Array::Unprofiled:
case Array::Undecided:
RELEASE_ASSERT_NOT_REACHED();
break;
case Array::Generic:
#if USE(JSVALUE32_64)
fixEdge<CellUse>(node->child1()); // Speculating cell due to register pressure on 32-bit.
#endif
break;
case Array::ForceExit:
break;
default:
fixEdge<KnownCellUse>(node->child1());
fixEdge<Int32Use>(node->child2());
break;
}
break;
}
case PutByValDirect:
case PutByVal:
case PutByValAlias: {
Edge& child1 = m_graph.varArgChild(node, 0);
Edge& child2 = m_graph.varArgChild(node, 1);
Edge& child3 = m_graph.varArgChild(node, 2);
node->setArrayMode(
node->arrayMode().refine(
m_graph, node->codeOrigin,
child1->prediction(),
child2->prediction(),
child3->prediction()));
blessArrayOperation(child1, child2, m_graph.varArgChild(node, 3));
switch (node->arrayMode().modeForPut().type()) {
case Array::SelectUsingPredictions:
case Array::Unprofiled:
case Array::Undecided:
RELEASE_ASSERT_NOT_REACHED();
break;
case Array::ForceExit:
case Array::Generic:
#if USE(JSVALUE32_64)
// Due to register pressure on 32-bit, we speculate cell and
// ignore the base-is-not-cell case entirely by letting the
// baseline JIT handle it.
fixEdge<CellUse>(child1);
#endif
break;
case Array::Int32:
fixEdge<KnownCellUse>(child1);
fixEdge<Int32Use>(child2);
fixEdge<Int32Use>(child3);
if (child3->prediction() & SpecInt52)
fixEdge<MachineIntUse>(child3);
else
fixEdge<Int32Use>(child3);
break;
case Array::Double:
fixEdge<KnownCellUse>(child1);
fixEdge<Int32Use>(child2);
fixEdge<RealNumberUse>(child3);
break;
case Array::Int8Array:
case Array::Int16Array:
case Array::Int32Array:
case Array::Uint8Array:
case Array::Uint8ClampedArray:
case Array::Uint16Array:
case Array::Uint32Array:
fixEdge<KnownCellUse>(child1);
fixEdge<Int32Use>(child2);
if (child3->shouldSpeculateInt32())
fixEdge<Int32Use>(child3);
else if (child3->shouldSpeculateMachineInt())
fixEdge<MachineIntUse>(child3);
else
fixEdge<NumberUse>(child3);
break;
case Array::Float32Array:
case Array::Float64Array:
fixEdge<KnownCellUse>(child1);
fixEdge<Int32Use>(child2);
fixEdge<NumberUse>(child3);
break;
case Array::Contiguous:
case Array::ArrayStorage:
case Array::SlowPutArrayStorage:
case Array::Arguments:
fixEdge<KnownCellUse>(child1);
fixEdge<Int32Use>(child2);
insertStoreBarrier(m_indexInBlock, child1, child3);
break;
default:
fixEdge<KnownCellUse>(child1);
fixEdge<Int32Use>(child2);
break;
}
break;
}
case ArrayPush: {
// May need to refine the array mode in case the value prediction contravenes
// the array prediction. For example, we may have evidence showing that the
// array is in Int32 mode, but the value we're storing is likely to be a double.
// Then we should turn this into a conversion to Double array followed by the
// push. On the other hand, we absolutely don't want to refine based on the
// base prediction. If it has non-cell garbage in it, then we want that to be
// ignored. That's because ArrayPush can't handle any array modes that aren't
// array-related - so if refine() turned this into a "Generic" ArrayPush then
// that would break things.
node->setArrayMode(
node->arrayMode().refine(
m_graph, node->codeOrigin,
node->child1()->prediction() & SpecCell,
SpecInt32,
node->child2()->prediction()));
blessArrayOperation(node->child1(), Edge(), node->child3());
fixEdge<KnownCellUse>(node->child1());
switch (node->arrayMode().type()) {
case Array::Int32:
fixEdge<Int32Use>(node->child2());
break;
case Array::Double:
fixEdge<RealNumberUse>(node->child2());
break;
case Array::Contiguous:
case Array::ArrayStorage:
insertStoreBarrier(m_indexInBlock, node->child1(), node->child2());
break;
default:
break;
}
break;
}
case ArrayPop: {
blessArrayOperation(node->child1(), Edge(), node->child2());
fixEdge<KnownCellUse>(node->child1());
break;
}
case RegExpExec:
case RegExpTest: {
fixEdge<CellUse>(node->child1());
fixEdge<CellUse>(node->child2());
break;
}
case Branch: {
if (node->child1()->shouldSpeculateBoolean())
fixEdge<BooleanUse>(node->child1());
else if (node->child1()->shouldSpeculateObjectOrOther())
fixEdge<ObjectOrOtherUse>(node->child1());
else if (node->child1()->shouldSpeculateInt32())
fixEdge<Int32Use>(node->child1());
else if (node->child1()->shouldSpeculateNumber())
fixEdge<NumberUse>(node->child1());
Node* logicalNot = node->child1().node();
if (logicalNot->op() == LogicalNot) {
// Make sure that OSR exit can't observe the LogicalNot. If it can,
// then we must compute it and cannot peephole around it.
bool found = false;
bool ok = true;
for (unsigned i = m_indexInBlock; i--;) {
Node* candidate = m_block->at(i);
if (candidate == logicalNot) {
found = true;
break;
}
if (candidate->canExit()) {
ok = false;
found = true;
break;
}
}
ASSERT_UNUSED(found, found);
if (ok) {
Edge newChildEdge = logicalNot->child1();
if (newChildEdge->hasBooleanResult()) {
node->children.setChild1(newChildEdge);
BasicBlock* toBeTaken = node->notTakenBlock();
BasicBlock* toBeNotTaken = node->takenBlock();
node->setTakenBlock(toBeTaken);
node->setNotTakenBlock(toBeNotTaken);
}
}
}
break;
}
case Switch: {
SwitchData* data = node->switchData();
switch (data->kind) {
case SwitchImm:
if (node->child1()->shouldSpeculateInt32())
fixEdge<Int32Use>(node->child1());
break;
case SwitchChar:
if (node->child1()->shouldSpeculateString())
fixEdge<StringUse>(node->child1());
break;
case SwitchString:
if (node->child1()->shouldSpeculateStringIdent())
fixEdge<StringIdentUse>(node->child1());
else if (node->child1()->shouldSpeculateString())
fixEdge<StringUse>(node->child1());
break;
}
break;
}
case ToPrimitive: {
fixupToPrimitive(node);
break;
}
case ToString: {
fixupToString(node);
break;
}
case NewStringObject: {
fixEdge<KnownStringUse>(node->child1());
break;
}
case NewArray: {
for (unsigned i = m_graph.varArgNumChildren(node); i--;) {
node->setIndexingType(
leastUpperBoundOfIndexingTypeAndType(
node->indexingType(), m_graph.varArgChild(node, i)->prediction()));
}
switch (node->indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
CRASH();
break;
case ALL_UNDECIDED_INDEXING_TYPES:
if (node->numChildren()) {
// This will only happen if the children have no type predictions. We
// would have already exited by now, but insert a forced exit just to
// be safe.
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, ForceOSRExit, node->codeOrigin);
}
break;
case ALL_INT32_INDEXING_TYPES:
for (unsigned operandIndex = 0; operandIndex < node->numChildren(); ++operandIndex)
fixEdge<Int32Use>(m_graph.m_varArgChildren[node->firstChild() + operandIndex]);
break;
case ALL_DOUBLE_INDEXING_TYPES:
for (unsigned operandIndex = 0; operandIndex < node->numChildren(); ++operandIndex)
fixEdge<RealNumberUse>(m_graph.m_varArgChildren[node->firstChild() + operandIndex]);
break;
case ALL_CONTIGUOUS_INDEXING_TYPES:
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
break;
default:
CRASH();
break;
}
break;
}
case NewTypedArray: {
if (node->child1()->shouldSpeculateInt32()) {
fixEdge<Int32Use>(node->child1());
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
break;
}
break;
}
case NewArrayWithSize: {
fixEdge<Int32Use>(node->child1());
break;
}
case ToThis: {
ECMAMode ecmaMode = m_graph.executableFor(node->codeOrigin)->isStrictMode() ? StrictMode : NotStrictMode;
if (isOtherSpeculation(node->child1()->prediction())) {
if (ecmaMode == StrictMode) {
fixEdge<OtherUse>(node->child1());
node->convertToIdentity();
break;
}
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, Phantom, node->codeOrigin,
Edge(node->child1().node(), OtherUse));
observeUseKindOnNode<OtherUse>(node->child1().node());
node->convertToWeakConstant(m_graph.globalThisObjectFor(node->codeOrigin));
break;
}
if (isFinalObjectSpeculation(node->child1()->prediction())) {
fixEdge<FinalObjectUse>(node->child1());
node->convertToIdentity();
break;
}
break;
}
case GetMyArgumentByVal:
case GetMyArgumentByValSafe: {
fixEdge<Int32Use>(node->child1());
break;
}
case PutStructure: {
fixEdge<KnownCellUse>(node->child1());
insertStoreBarrier(m_indexInBlock, node->child1());
break;
}
case PutClosureVar: {
fixEdge<KnownCellUse>(node->child1());
insertStoreBarrier(m_indexInBlock, node->child1(), node->child3());
break;
}
case GetClosureRegisters:
case SkipTopScope:
case SkipScope:
case GetScope: {
fixEdge<KnownCellUse>(node->child1());
break;
}
case AllocatePropertyStorage:
case ReallocatePropertyStorage: {
fixEdge<KnownCellUse>(node->child1());
insertStoreBarrier(m_indexInBlock + 1, node->child1());
break;
}
case GetById:
case GetByIdFlush: {
if (!node->child1()->shouldSpeculateCell())
break;
StringImpl* impl = m_graph.identifiers()[node->identifierNumber()];
if (impl == vm().propertyNames->length.impl()) {
attemptToMakeGetArrayLength(node);
break;
}
if (impl == vm().propertyNames->byteLength.impl()) {
attemptToMakeGetTypedArrayByteLength(node);
break;
}
if (impl == vm().propertyNames->byteOffset.impl()) {
attemptToMakeGetTypedArrayByteOffset(node);
break;
}
fixEdge<CellUse>(node->child1());
break;
}
case PutById:
case PutByIdDirect: {
fixEdge<CellUse>(node->child1());
insertStoreBarrier(m_indexInBlock, node->child1(), node->child2());
break;
}
case CheckExecutable:
case CheckStructure:
case StructureTransitionWatchpoint:
case CheckFunction:
case CheckHasInstance:
case CreateThis:
case GetButterfly: {
fixEdge<CellUse>(node->child1());
break;
}
case Arrayify:
case ArrayifyToStructure: {
fixEdge<CellUse>(node->child1());
if (node->child2())
fixEdge<Int32Use>(node->child2());
break;
}
case GetByOffset: {
if (!node->child1()->hasStorageResult())
fixEdge<KnownCellUse>(node->child1());
fixEdge<KnownCellUse>(node->child2());
break;
}
case PutByOffset: {
if (!node->child1()->hasStorageResult())
fixEdge<KnownCellUse>(node->child1());
fixEdge<KnownCellUse>(node->child2());
insertStoreBarrier(m_indexInBlock, node->child2(), node->child3());
break;
}
case InstanceOf: {
// FIXME: This appears broken: CheckHasInstance already does an unconditional cell
// check. https://bugs.webkit.org/show_bug.cgi?id=107479
if (!(node->child1()->prediction() & ~SpecCell))
fixEdge<CellUse>(node->child1());
fixEdge<CellUse>(node->child2());
break;
}
case In: {
// FIXME: We should at some point have array profiling on op_in, in which
// case we would be able to turn this into a kind of GetByVal.
fixEdge<CellUse>(node->child2());
break;
}
case Phantom:
case Identity:
case Check: {
switch (node->child1().useKind()) {
case NumberUse:
if (node->child1()->shouldSpeculateInt32ForArithmetic())
node->child1().setUseKind(Int32Use);
break;
default:
break;
}
observeUseKindOnEdge(node->child1());
break;
}
case GetArrayLength:
case Phi:
case Upsilon:
case GetArgument:
case PhantomPutStructure:
case GetIndexedPropertyStorage:
case GetTypedArrayByteOffset:
case LastNodeType:
case CheckTierUpInLoop:
case CheckTierUpAtReturn:
case CheckTierUpAndOSREnter:
case Int52ToDouble:
case Int52ToValue:
case InvalidationPoint:
case CheckArray:
case CheckInBounds:
case ConstantStoragePointer:
case DoubleAsInt32:
case Int32ToDouble:
case ValueToInt32:
// These are just nodes that we don't currently expect to see during fixup.
// If we ever wanted to insert them prior to fixup, then we just have to create
// fixup rules for them.
RELEASE_ASSERT_NOT_REACHED();
break;
case PutGlobalVar: {
Node* globalObjectNode = m_insertionSet.insertNode(m_indexInBlock, SpecNone, WeakJSConstant, node->codeOrigin,
OpInfo(m_graph.globalObjectFor(node->codeOrigin)));
Node* barrierNode = m_graph.addNode(SpecNone, ConditionalStoreBarrier, m_currentNode->codeOrigin,
Edge(globalObjectNode, KnownCellUse), Edge(node->child1().node(), UntypedUse));
m_insertionSet.insert(m_indexInBlock, barrierNode);
break;
}
case TearOffActivation: {
Node* barrierNode = m_graph.addNode(SpecNone, StoreBarrierWithNullCheck, m_currentNode->codeOrigin,
Edge(node->child1().node(), UntypedUse));
m_insertionSet.insert(m_indexInBlock, barrierNode);
break;
}
case IsString:
if (node->child1()->shouldSpeculateString()) {
m_insertionSet.insertNode(m_indexInBlock, SpecNone, Phantom, node->codeOrigin,
Edge(node->child1().node(), StringUse));
m_graph.convertToConstant(node, jsBoolean(true));
observeUseKindOnNode<StringUse>(node);
}
break;
#if !ASSERT_DISABLED
// Have these no-op cases here to ensure that nobody forgets to add handlers for new opcodes.
case SetArgument:
case JSConstant:
case WeakJSConstant:
case GetLocal:
case GetCallee:
case Flush:
case PhantomLocal:
case GetLocalUnlinked:
case GetMyScope:
case GetClosureVar:
case GetGlobalVar:
case NotifyWrite:
case VariableWatchpoint:
case VarInjectionWatchpoint:
case AllocationProfileWatchpoint:
case Call:
case Construct:
case NewObject:
case NewArrayBuffer:
case NewRegexp:
case Breakpoint:
case ProfileWillCall:
case ProfileDidCall:
case IsUndefined:
case IsBoolean:
case IsNumber:
case IsObject:
case IsFunction:
case CreateActivation:
case CreateArguments:
case PhantomArguments:
case TearOffArguments:
case GetMyArgumentsLength:
case GetMyArgumentsLengthSafe:
case CheckArgumentsNotCreated:
case NewFunction:
case NewFunctionNoCheck:
case NewFunctionExpression:
case Jump:
case Return:
case Throw:
case ThrowReferenceError:
case CountExecution:
case ForceOSRExit:
case CheckWatchdogTimer:
case Unreachable:
case ExtractOSREntryLocal:
case LoopHint:
case StoreBarrier:
case ConditionalStoreBarrier:
case StoreBarrierWithNullCheck:
case FunctionReentryWatchpoint:
case TypedArrayWatchpoint:
case MovHint:
case ZombieHint:
break;
#else
default:
break;
#endif
}
if (!node->containsMovHint())
DFG_NODE_DO_TO_CHILDREN(m_graph, node, observeUntypedEdge);
if (node->isTerminal()) {
// Terminal nodes don't need post-phantoms, and inserting them would violate
// the current requirement that a terminal is the last thing in a block. We
// should eventually change that requirement but even if we did, this would
// still be a valid optimization. All terminals accept just one input, and
// if that input is a conversion node then no further speculations will be
// performed.
// FIXME: Get rid of this by allowing Phantoms after terminals.
// https://bugs.webkit.org/show_bug.cgi?id=126778
m_requiredPhantoms.resize(0);
// Since StoreBarriers are recursively fixed up so that their children look
// identical to that of the node they're barrier-ing, we need to avoid adding
// any Phantoms when processing them because this would invalidate the
// InsertionSet's invariant of inserting things in a monotonically increasing
// order. This should be okay anyways because StoreBarriers can't exit.
} else
addPhantomsIfNecessary();
}
void observeUntypedEdge(Node*, Edge& edge)
{
if (edge.useKind() != UntypedUse)
return;
fixEdge<UntypedUse>(edge);
}
template<UseKind useKind>
void createToString(Node* node, Edge& edge)
{
edge.setNode(m_insertionSet.insertNode(
m_indexInBlock, SpecString, ToString, node->codeOrigin,
Edge(edge.node(), useKind)));
}
template<UseKind useKind>
void attemptToForceStringArrayModeByToStringConversion(ArrayMode& arrayMode, Node* node)
{
ASSERT(arrayMode == ArrayMode(Array::Generic));
if (!canOptimizeStringObjectAccess(node->codeOrigin))
return;
createToString<useKind>(node, node->child1());
arrayMode = ArrayMode(Array::String);
}
template<UseKind useKind>
bool isStringObjectUse()
{
switch (useKind) {
case StringObjectUse:
case StringOrStringObjectUse:
return true;
default:
return false;
}
}
template<UseKind useKind>
void convertStringAddUse(Node* node, Edge& edge)
{
if (useKind == StringUse) {
// This preserves the binaryUseKind() invariant ot ValueAdd: ValueAdd's
// two edges will always have identical use kinds, which makes the
// decision process much easier.
observeUseKindOnNode<StringUse>(edge.node());
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, Phantom, node->codeOrigin,
Edge(edge.node(), StringUse));
edge.setUseKind(KnownStringUse);
return;
}
// FIXME: We ought to be able to have a ToPrimitiveToString node.
observeUseKindOnNode<useKind>(edge.node());
createToString<useKind>(node, edge);
}
void convertToMakeRope(Node* node)
{
node->setOpAndDefaultFlags(MakeRope);
fixupMakeRope(node);
}
void fixupMakeRope(Node* node)
{
for (unsigned i = 0; i < AdjacencyList::Size; ++i) {
Edge& edge = node->children.child(i);
if (!edge)
break;
edge.setUseKind(KnownStringUse);
if (!m_graph.isConstant(edge.node()))
continue;
JSString* string = jsCast<JSString*>(m_graph.valueOfJSConstant(edge.node()).asCell());
if (string->length())
continue;
// Don't allow the MakeRope to have zero children.
if (!i && !node->child2())
break;
node->children.removeEdge(i--);
}
if (!node->child2()) {
ASSERT(!node->child3());
node->convertToIdentity();
}
}
void fixupToPrimitive(Node* node)
{
if (node->child1()->shouldSpeculateInt32()) {
fixEdge<Int32Use>(node->child1());
node->convertToIdentity();
return;
}
if (node->child1()->shouldSpeculateString()) {
fixEdge<StringUse>(node->child1());
node->convertToIdentity();
return;
}
if (node->child1()->shouldSpeculateStringObject()
&& canOptimizeStringObjectAccess(node->codeOrigin)) {
fixEdge<StringObjectUse>(node->child1());
node->convertToToString();
return;
}
if (node->child1()->shouldSpeculateStringOrStringObject()
&& canOptimizeStringObjectAccess(node->codeOrigin)) {
fixEdge<StringOrStringObjectUse>(node->child1());
node->convertToToString();
return;
}
}
void fixupToString(Node* node)
{
if (node->child1()->shouldSpeculateString()) {
fixEdge<StringUse>(node->child1());
node->convertToIdentity();
return;
}
if (node->child1()->shouldSpeculateStringObject()
&& canOptimizeStringObjectAccess(node->codeOrigin)) {
fixEdge<StringObjectUse>(node->child1());
return;
}
if (node->child1()->shouldSpeculateStringOrStringObject()
&& canOptimizeStringObjectAccess(node->codeOrigin)) {
fixEdge<StringOrStringObjectUse>(node->child1());
return;
}
if (node->child1()->shouldSpeculateCell()) {
fixEdge<CellUse>(node->child1());
return;
}
}
template<UseKind leftUseKind>
bool attemptToMakeFastStringAdd(Node* node, Edge& left, Edge& right)
{
Node* originalLeft = left.node();
Node* originalRight = right.node();
ASSERT(leftUseKind == StringUse || leftUseKind == StringObjectUse || leftUseKind == StringOrStringObjectUse);
if (isStringObjectUse<leftUseKind>() && !canOptimizeStringObjectAccess(node->codeOrigin))
return false;
convertStringAddUse<leftUseKind>(node, left);
if (right->shouldSpeculateString())
convertStringAddUse<StringUse>(node, right);
else if (right->shouldSpeculateStringObject() && canOptimizeStringObjectAccess(node->codeOrigin))
convertStringAddUse<StringObjectUse>(node, right);
else if (right->shouldSpeculateStringOrStringObject() && canOptimizeStringObjectAccess(node->codeOrigin))
convertStringAddUse<StringOrStringObjectUse>(node, right);
else {
// At this point we know that the other operand is something weird. The semantically correct
// way of dealing with this is:
//
// MakeRope(@left, ToString(ToPrimitive(@right)))
//
// So that's what we emit. NB, we need to do all relevant type checks on @left before we do
// anything to @right, since ToPrimitive may be effectful.
Node* toPrimitive = m_insertionSet.insertNode(
m_indexInBlock, resultOfToPrimitive(right->prediction()), ToPrimitive, node->codeOrigin,
Edge(right.node()));
Node* toString = m_insertionSet.insertNode(
m_indexInBlock, SpecString, ToString, node->codeOrigin, Edge(toPrimitive));
fixupToPrimitive(toPrimitive);
fixupToString(toString);
right.setNode(toString);
}
// We're doing checks up there, so we need to make sure that the
// *original* inputs to the addition are live up to here.
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, Phantom, node->codeOrigin,
Edge(originalLeft), Edge(originalRight));
convertToMakeRope(node);
return true;
}
bool isStringPrototypeMethodSane(Structure* stringPrototypeStructure, StringImpl* uid)
{
unsigned attributesUnused;
JSCell* specificValue;
PropertyOffset offset = stringPrototypeStructure->getConcurrently(
vm(), uid, attributesUnused, specificValue);
if (!isValidOffset(offset))
return false;
if (!specificValue)
return false;
if (!specificValue->inherits(JSFunction::info()))
return false;
JSFunction* function = jsCast<JSFunction*>(specificValue);
if (function->executable()->intrinsicFor(CodeForCall) != StringPrototypeValueOfIntrinsic)
return false;
return true;
}
bool canOptimizeStringObjectAccess(const CodeOrigin& codeOrigin)
{
if (m_graph.hasExitSite(codeOrigin, NotStringObject))
return false;
Structure* stringObjectStructure = m_graph.globalObjectFor(codeOrigin)->stringObjectStructure();
ASSERT(stringObjectStructure->storedPrototype().isObject());
ASSERT(stringObjectStructure->storedPrototype().asCell()->classInfo() == StringPrototype::info());
JSObject* stringPrototypeObject = asObject(stringObjectStructure->storedPrototype());
Structure* stringPrototypeStructure = stringPrototypeObject->structure();
if (!m_graph.watchpoints().isStillValid(stringPrototypeStructure->transitionWatchpointSet()))
return false;
if (stringPrototypeStructure->isDictionary())
return false;
// We're being conservative here. We want DFG's ToString on StringObject to be
// used in both numeric contexts (that would call valueOf()) and string contexts
// (that would call toString()). We don't want the DFG to have to distinguish
// between the two, just because that seems like it would get confusing. So we
// just require both methods to be sane.
if (!isStringPrototypeMethodSane(stringPrototypeStructure, vm().propertyNames->valueOf.impl()))
return false;
if (!isStringPrototypeMethodSane(stringPrototypeStructure, vm().propertyNames->toString.impl()))
return false;
return true;
}
void fixupSetLocalsInBlock(BasicBlock* block)
{
if (!block)
return;
ASSERT(block->isReachable);
m_block = block;
for (m_indexInBlock = 0; m_indexInBlock < block->size(); ++m_indexInBlock) {
Node* node = m_currentNode = block->at(m_indexInBlock);
if (node->op() != SetLocal)
continue;
VariableAccessData* variable = node->variableAccessData();
switch (variable->flushFormat()) {
case FlushedJSValue:
break;
case FlushedDouble:
fixEdge<NumberUse>(node->child1());
break;
case FlushedInt32:
fixEdge<Int32Use>(node->child1());
break;
case FlushedInt52:
fixEdge<MachineIntUse>(node->child1());
break;
case FlushedCell:
fixEdge<CellUse>(node->child1());
break;
case FlushedBoolean:
fixEdge<BooleanUse>(node->child1());
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
addPhantomsIfNecessary();
}
m_insertionSet.execute(block);
}
void fixupUntypedSetLocalsInBlock(BasicBlock* block)
{
if (!block)
return;
ASSERT(block->isReachable);
m_block = block;
for (m_indexInBlock = 0; m_indexInBlock < block->size(); ++m_indexInBlock) {
Node* node = m_currentNode = block->at(m_indexInBlock);
if (node->op() != SetLocal)
continue;
if (node->child1().useKind() == UntypedUse) {
fixEdge<UntypedUse>(node->child1());
addPhantomsIfNecessary();
}
}
m_insertionSet.execute(block);
}
Node* checkArray(ArrayMode arrayMode, const CodeOrigin& codeOrigin, Node* array, Node* index, bool (*storageCheck)(const ArrayMode&) = canCSEStorage)
{
ASSERT(arrayMode.isSpecific());
if (arrayMode.type() == Array::String) {
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, Phantom, codeOrigin,
Edge(array, StringUse));
} else {
Structure* structure = arrayMode.originalArrayStructure(m_graph, codeOrigin);
Edge indexEdge = index ? Edge(index, Int32Use) : Edge();
if (arrayMode.doesConversion()) {
if (structure) {
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, ArrayifyToStructure, codeOrigin,
OpInfo(structure), OpInfo(arrayMode.asWord()), Edge(array, CellUse), indexEdge);
} else {
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, Arrayify, codeOrigin,
OpInfo(arrayMode.asWord()), Edge(array, CellUse), indexEdge);
}
} else {
if (structure) {
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, CheckStructure, codeOrigin,
OpInfo(m_graph.addStructureSet(structure)), Edge(array, CellUse));
} else {
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, CheckArray, codeOrigin,
OpInfo(arrayMode.asWord()), Edge(array, CellUse));
}
}
}
if (!storageCheck(arrayMode))
return 0;
if (arrayMode.usesButterfly()) {
return m_insertionSet.insertNode(
m_indexInBlock, SpecNone, GetButterfly, codeOrigin, Edge(array, CellUse));
}
return m_insertionSet.insertNode(
m_indexInBlock, SpecNone, GetIndexedPropertyStorage, codeOrigin,
OpInfo(arrayMode.asWord()), Edge(array, KnownCellUse));
}
void blessArrayOperation(Edge base, Edge index, Edge& storageChild)
{
Node* node = m_currentNode;
switch (node->arrayMode().type()) {
case Array::ForceExit: {
m_insertionSet.insertNode(
m_indexInBlock, SpecNone, ForceOSRExit, node->codeOrigin);
return;
}
case Array::SelectUsingPredictions:
case Array::Unprofiled:
RELEASE_ASSERT_NOT_REACHED();
return;
case Array::Generic:
return;
default: {
Node* storage = checkArray(node->arrayMode(), node->codeOrigin, base.node(), index.node());
if (!storage)
return;
storageChild = Edge(storage);
return;
} }
}
bool alwaysUnboxSimplePrimitives()
{
#if USE(JSVALUE64)
return false;
#else
// Any boolean, int, or cell value is profitable to unbox on 32-bit because it
// reduces traffic.
return true;
#endif
}
template<UseKind useKind>
void observeUseKindOnNode(Node* node)
{
if (useKind == UntypedUse)
return;
observeUseKindOnNode(node, useKind);
}
void observeUseKindOnEdge(Edge edge)
{
observeUseKindOnNode(edge.node(), edge.useKind());
}
void observeUseKindOnNode(Node* node, UseKind useKind)
{
if (node->op() != GetLocal)
return;
// FIXME: The way this uses alwaysUnboxSimplePrimitives() is suspicious.
// https://bugs.webkit.org/show_bug.cgi?id=121518
VariableAccessData* variable = node->variableAccessData();
switch (useKind) {
case Int32Use:
if (alwaysUnboxSimplePrimitives()
|| isInt32Speculation(variable->prediction()))
m_profitabilityChanged |= variable->mergeIsProfitableToUnbox(true);
break;
case NumberUse:
case RealNumberUse:
if (variable->doubleFormatState() == UsingDoubleFormat)
m_profitabilityChanged |= variable->mergeIsProfitableToUnbox(true);
break;
case BooleanUse:
if (alwaysUnboxSimplePrimitives()
|| isBooleanSpeculation(variable->prediction()))
m_profitabilityChanged |= variable->mergeIsProfitableToUnbox(true);
break;
case MachineIntUse:
if (isMachineIntSpeculation(variable->prediction()))
m_profitabilityChanged |= variable->mergeIsProfitableToUnbox(true);
break;
case CellUse:
case KnownCellUse:
case ObjectUse:
case StringUse:
case KnownStringUse:
case StringObjectUse:
case StringOrStringObjectUse:
if (alwaysUnboxSimplePrimitives()
|| isCellSpeculation(variable->prediction()))
m_profitabilityChanged |= variable->mergeIsProfitableToUnbox(true);
break;
default:
break;
}
}
// Set the use kind of the edge and perform any actions that need to be done for
// that use kind, like inserting intermediate conversion nodes. Never call this
// with useKind = UntypedUse explicitly; edges have UntypedUse implicitly and any
// edge that survives fixup and still has UntypedUse will have this method called
// from observeUntypedEdge(). Also, make sure that if you do change the type of an
// edge, you either call fixEdge() or perform the equivalent functionality
// yourself. Obviously, you should have a really good reason if you do the latter.
template<UseKind useKind>
void fixEdge(Edge& edge)
{
if (isDouble(useKind)) {
if (edge->shouldSpeculateInt32ForArithmetic()) {
injectInt32ToDoubleNode(edge, useKind);
return;
}
if (enableInt52() && edge->shouldSpeculateMachineInt()) {
// Make all double uses of int52 values have an intermediate Int52ToDouble.
// This is for the same reason as Int52ToValue (see below) except that
// Int8ToDouble will convert int52's that fit in an int32 into a double
// rather than trying to create a boxed int32 like Int52ToValue does.
m_requiredPhantoms.append(edge.node());
Node* result = m_insertionSet.insertNode(
m_indexInBlock, SpecInt52AsDouble, Int52ToDouble,
m_currentNode->codeOrigin, Edge(edge.node(), NumberUse));
edge = Edge(result, useKind);
return;
}
}
if (enableInt52() && useKind != MachineIntUse
&& edge->shouldSpeculateMachineInt() && !edge->shouldSpeculateInt32()) {
// We make all non-int52 uses of int52 values have an intermediate Int52ToValue
// node to ensure that we handle this properly:
//
// a: SomeInt52
// b: ArithAdd(@a, ...)
// c: Call(..., @a)
// d: ArithAdd(@a, ...)
//
// Without an intermediate node and just labeling the uses, we will get:
//
// a: SomeInt52
// b: ArithAdd(Int52:@a, ...)
// c: Call(..., Untyped:@a)
// d: ArithAdd(Int52:@a, ...)
//
// And now the c->Untyped:@a edge will box the value of @a into a double. This
// is bad, because now the d->Int52:@a edge will either have to do double-to-int
// conversions, or will have to OSR exit unconditionally. Alternatively we could
// have the c->Untyped:@a edge box the value by copying rather than in-place.
// But these boxings are also costly so this wouldn't be great.
//
// The solution we use is to always have non-Int52 uses of predicted Int52's use
// an intervening Int52ToValue node:
//
// a: SomeInt52
// b: ArithAdd(Int52:@a, ...)
// x: Int52ToValue(Int52:@a)
// c: Call(..., Untyped:@x)
// d: ArithAdd(Int52:@a, ...)
//
// Note that even if we had multiple non-int52 uses of @a, the multiple
// Int52ToValue's would get CSE'd together. So the boxing would only happen once.
// At the same time, @a would continue to be represented as a native int52.
//
// An alternative would have been to insert ToNativeInt52 nodes on int52 uses of
// int52's. This would have handled the above example but would fall over for:
//
// a: SomeInt52
// b: Call(..., @a)
// c: ArithAdd(@a, ...)
//
// But the solution we use handles the above gracefully.
m_requiredPhantoms.append(edge.node());
Node* result = m_insertionSet.insertNode(
m_indexInBlock, SpecInt52, Int52ToValue,
m_currentNode->codeOrigin, Edge(edge.node(), UntypedUse));
edge = Edge(result, useKind);
return;
}
observeUseKindOnNode<useKind>(edge.node());
edge.setUseKind(useKind);
}
void insertStoreBarrier(unsigned indexInBlock, Edge child1, Edge child2 = Edge())
{
Node* barrierNode;
if (!child2)
barrierNode = m_graph.addNode(SpecNone, StoreBarrier, m_currentNode->codeOrigin, Edge(child1.node(), child1.useKind()));
else {
barrierNode = m_graph.addNode(SpecNone, ConditionalStoreBarrier, m_currentNode->codeOrigin,
Edge(child1.node(), child1.useKind()), Edge(child2.node(), child2.useKind()));
}
m_insertionSet.insert(indexInBlock, barrierNode);
}
void fixIntEdge(Edge& edge)
{
Node* node = edge.node();
if (node->shouldSpeculateInt32()) {
fixEdge<Int32Use>(edge);
return;
}
UseKind useKind;
if (node->shouldSpeculateMachineInt())
useKind = MachineIntUse;
else if (node->shouldSpeculateNumber())
useKind = NumberUse;
else if (node->shouldSpeculateBoolean())
useKind = BooleanUse;
else
useKind = NotCellUse;
Node* newNode = m_insertionSet.insertNode(
m_indexInBlock, SpecInt32, ValueToInt32, m_currentNode->codeOrigin,
Edge(node, useKind));
observeUseKindOnNode(node, useKind);
edge = Edge(newNode, KnownInt32Use);
m_requiredPhantoms.append(node);
}
void injectInt32ToDoubleNode(Edge& edge, UseKind useKind = NumberUse)
{
m_requiredPhantoms.append(edge.node());
Node* result = m_insertionSet.insertNode(
m_indexInBlock, SpecInt52AsDouble, Int32ToDouble,
m_currentNode->codeOrigin, Edge(edge.node(), NumberUse));
edge = Edge(result, useKind);
}
void truncateConstantToInt32(Edge& edge)
{
Node* oldNode = edge.node();
ASSERT(oldNode->hasConstant());
JSValue value = m_graph.valueOfJSConstant(oldNode);
if (value.isInt32())
return;
value = jsNumber(JSC::toInt32(value.asNumber()));
ASSERT(value.isInt32());
unsigned constantRegister;
if (!codeBlock()->findConstant(value, constantRegister)) {
constantRegister = codeBlock()->addConstantLazily();
initializeLazyWriteBarrierForConstant(
m_graph.m_plan.writeBarriers,
codeBlock()->constants()[constantRegister],
codeBlock(),
constantRegister,
codeBlock()->ownerExecutable(),
value);
}
edge.setNode(m_insertionSet.insertNode(
m_indexInBlock, SpecInt32, JSConstant, m_currentNode->codeOrigin,
OpInfo(constantRegister)));
}
void truncateConstantsIfNecessary(Node* node, AddSpeculationMode mode)
{
if (mode != SpeculateInt32AndTruncateConstants)
return;
ASSERT(node->child1()->hasConstant() || node->child2()->hasConstant());
if (node->child1()->hasConstant())
truncateConstantToInt32(node->child1());
else
truncateConstantToInt32(node->child2());
}
bool attemptToMakeIntegerAdd(Node* node)
{
AddSpeculationMode mode = m_graph.addSpeculationMode(node);
if (mode != DontSpeculateInt32) {
truncateConstantsIfNecessary(node, mode);
fixEdge<Int32Use>(node->child1());
fixEdge<Int32Use>(node->child2());
if (bytecodeCanTruncateInteger(node->arithNodeFlags()))
node->setArithMode(Arith::Unchecked);
else
node->setArithMode(Arith::CheckOverflow);
return true;
}
if (m_graph.addShouldSpeculateMachineInt(node)) {
fixEdge<MachineIntUse>(node->child1());
fixEdge<MachineIntUse>(node->child2());
node->setArithMode(Arith::CheckOverflow);
return true;
}
return false;
}
bool attemptToMakeGetArrayLength(Node* node)
{
if (!isInt32Speculation(node->prediction()))
return false;
CodeBlock* profiledBlock = m_graph.baselineCodeBlockFor(node->codeOrigin);
ArrayProfile* arrayProfile =
profiledBlock->getArrayProfile(node->codeOrigin.bytecodeIndex);
ArrayMode arrayMode = ArrayMode(Array::SelectUsingPredictions);
if (arrayProfile) {
ConcurrentJITLocker locker(profiledBlock->m_lock);
arrayProfile->computeUpdatedPrediction(locker, profiledBlock);
arrayMode = ArrayMode::fromObserved(locker, arrayProfile, Array::Read, false);
if (arrayMode.type() == Array::Unprofiled) {
// For normal array operations, it makes sense to treat Unprofiled
// accesses as ForceExit and get more data rather than using
// predictions and then possibly ending up with a Generic. But here,
// we treat anything that is Unprofiled as Generic and keep the
// GetById. I.e. ForceExit = Generic. So, there is no harm - and only
// profit - from treating the Unprofiled case as
// SelectUsingPredictions.
arrayMode = ArrayMode(Array::SelectUsingPredictions);
}
}
arrayMode = arrayMode.refine(
m_graph, node->codeOrigin, node->child1()->prediction(), node->prediction());
if (arrayMode.type() == Array::Generic) {
// Check if the input is something that we can't get array length for, but for which we
// could insert some conversions in order to transform it into something that we can do it
// for.
if (node->child1()->shouldSpeculateStringObject())
attemptToForceStringArrayModeByToStringConversion<StringObjectUse>(arrayMode, node);
else if (node->child1()->shouldSpeculateStringOrStringObject())
attemptToForceStringArrayModeByToStringConversion<StringOrStringObjectUse>(arrayMode, node);
}
if (!arrayMode.supportsLength())
return false;
convertToGetArrayLength(node, arrayMode);
return true;
}
bool attemptToMakeGetTypedArrayByteLength(Node* node)
{
if (!isInt32Speculation(node->prediction()))
return false;
TypedArrayType type = typedArrayTypeFromSpeculation(node->child1()->prediction());
if (!isTypedView(type))
return false;
if (elementSize(type) == 1) {
convertToGetArrayLength(node, ArrayMode(toArrayType(type)));
return true;
}
Node* length = prependGetArrayLength(
node->codeOrigin, node->child1().node(), ArrayMode(toArrayType(type)));
Node* shiftAmount = m_insertionSet.insertNode(
m_indexInBlock, SpecInt32, JSConstant, node->codeOrigin,
OpInfo(m_graph.constantRegisterForConstant(jsNumber(logElementSize(type)))));
// We can use a BitLShift here because typed arrays will never have a byteLength
// that overflows int32.
node->setOp(BitLShift);
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
observeUseKindOnNode(length, Int32Use);
observeUseKindOnNode(shiftAmount, Int32Use);
node->child1() = Edge(length, Int32Use);
node->child2() = Edge(shiftAmount, Int32Use);
return true;
}
void convertToGetArrayLength(Node* node, ArrayMode arrayMode)
{
node->setOp(GetArrayLength);
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
fixEdge<KnownCellUse>(node->child1());
node->setArrayMode(arrayMode);
Node* storage = checkArray(arrayMode, node->codeOrigin, node->child1().node(), 0, lengthNeedsStorage);
if (!storage)
return;
node->child2() = Edge(storage);
}
Node* prependGetArrayLength(CodeOrigin codeOrigin, Node* child, ArrayMode arrayMode)
{
Node* storage = checkArray(arrayMode, codeOrigin, child, 0, lengthNeedsStorage);
return m_insertionSet.insertNode(
m_indexInBlock, SpecInt32, GetArrayLength, codeOrigin,
OpInfo(arrayMode.asWord()), Edge(child, KnownCellUse), Edge(storage));
}
bool attemptToMakeGetTypedArrayByteOffset(Node* node)
{
if (!isInt32Speculation(node->prediction()))
return false;
TypedArrayType type = typedArrayTypeFromSpeculation(node->child1()->prediction());
if (!isTypedView(type))
return false;
checkArray(
ArrayMode(toArrayType(type)), node->codeOrigin, node->child1().node(),
0, neverNeedsStorage);
node->setOp(GetTypedArrayByteOffset);
node->clearFlags(NodeMustGenerate | NodeClobbersWorld);
fixEdge<KnownCellUse>(node->child1());
return true;
}
void addPhantomsIfNecessary()
{
if (m_requiredPhantoms.isEmpty())
return;
for (unsigned i = m_requiredPhantoms.size(); i--;) {
m_insertionSet.insertNode(
m_indexInBlock + 1, SpecNone, Phantom, m_currentNode->codeOrigin,
Edge(m_requiredPhantoms[i], UntypedUse));
}
m_requiredPhantoms.resize(0);
}
BasicBlock* m_block;
unsigned m_indexInBlock;
Node* m_currentNode;
InsertionSet m_insertionSet;
bool m_profitabilityChanged;
Vector<Node*, 3> m_requiredPhantoms;
};
bool performFixup(Graph& graph)
{
SamplingRegion samplingRegion("DFG Fixup Phase");
return runPhase<FixupPhase>(graph);
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)