| /* |
| * Copyright (C) 2011, 2012 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 "DFGAbstractState.h" |
| |
| #if ENABLE(DFG_JIT) |
| |
| #include "CodeBlock.h" |
| #include "DFGBasicBlock.h" |
| #include "GetByIdStatus.h" |
| #include "PutByIdStatus.h" |
| |
| namespace JSC { namespace DFG { |
| |
| AbstractState::AbstractState(Graph& graph) |
| : m_codeBlock(graph.m_codeBlock) |
| , m_graph(graph) |
| , m_variables(m_codeBlock->numParameters(), graph.m_localVars) |
| , m_block(0) |
| { |
| m_nodes.resize(graph.size()); |
| } |
| |
| AbstractState::~AbstractState() { } |
| |
| void AbstractState::beginBasicBlock(BasicBlock* basicBlock) |
| { |
| ASSERT(!m_block); |
| |
| ASSERT(basicBlock->variablesAtHead.numberOfLocals() == basicBlock->valuesAtHead.numberOfLocals()); |
| ASSERT(basicBlock->variablesAtTail.numberOfLocals() == basicBlock->valuesAtTail.numberOfLocals()); |
| ASSERT(basicBlock->variablesAtHead.numberOfLocals() == basicBlock->variablesAtTail.numberOfLocals()); |
| |
| // This is usually a no-op, but it is possible that the graph has grown since the |
| // abstract state was last used. |
| m_nodes.resize(m_graph.size()); |
| |
| for (size_t i = 0; i < basicBlock->size(); i++) |
| m_nodes[basicBlock->at(i)].clear(); |
| |
| m_variables = basicBlock->valuesAtHead; |
| m_haveStructures = false; |
| for (size_t i = 0; i < m_variables.numberOfArguments(); ++i) { |
| if (m_variables.argument(i).m_currentKnownStructure.isNeitherClearNorTop()) { |
| m_haveStructures = true; |
| break; |
| } |
| } |
| for (size_t i = 0; i < m_variables.numberOfLocals(); ++i) { |
| if (m_variables.local(i).m_currentKnownStructure.isNeitherClearNorTop()) { |
| m_haveStructures = true; |
| break; |
| } |
| } |
| |
| basicBlock->cfaShouldRevisit = false; |
| basicBlock->cfaHasVisited = true; |
| m_block = basicBlock; |
| m_isValid = true; |
| m_foundConstants = false; |
| m_branchDirection = InvalidBranchDirection; |
| } |
| |
| void AbstractState::initialize(Graph& graph) |
| { |
| BasicBlock* root = graph.m_blocks[0].get(); |
| root->cfaShouldRevisit = true; |
| root->cfaHasVisited = false; |
| root->cfaFoundConstants = false; |
| for (size_t i = 0; i < root->valuesAtHead.numberOfArguments(); ++i) { |
| Node& node = graph[root->variablesAtHead.argument(i)]; |
| ASSERT(node.op() == SetArgument); |
| if (!node.shouldGenerate()) { |
| // The argument is dead. We don't do any checks for such arguments, and so |
| // for the purpose of the analysis, they contain no value. |
| root->valuesAtHead.argument(i).clear(); |
| continue; |
| } |
| |
| if (node.variableAccessData()->isCaptured()) { |
| root->valuesAtHead.argument(i).makeTop(); |
| continue; |
| } |
| |
| SpeculatedType prediction = node.variableAccessData()->prediction(); |
| if (isInt32Speculation(prediction)) |
| root->valuesAtHead.argument(i).set(SpecInt32); |
| else if (isBooleanSpeculation(prediction)) |
| root->valuesAtHead.argument(i).set(SpecBoolean); |
| else if (isCellSpeculation(prediction)) |
| root->valuesAtHead.argument(i).set(SpecCell); |
| else |
| root->valuesAtHead.argument(i).makeTop(); |
| |
| root->valuesAtTail.argument(i).clear(); |
| } |
| for (size_t i = 0; i < root->valuesAtHead.numberOfLocals(); ++i) { |
| NodeIndex nodeIndex = root->variablesAtHead.local(i); |
| if (nodeIndex != NoNode && graph[nodeIndex].variableAccessData()->isCaptured()) |
| root->valuesAtHead.local(i).makeTop(); |
| else |
| root->valuesAtHead.local(i).clear(); |
| root->valuesAtTail.local(i).clear(); |
| } |
| for (BlockIndex blockIndex = 1 ; blockIndex < graph.m_blocks.size(); ++blockIndex) { |
| BasicBlock* block = graph.m_blocks[blockIndex].get(); |
| if (!block) |
| continue; |
| if (!block->isReachable) |
| continue; |
| block->cfaShouldRevisit = false; |
| block->cfaHasVisited = false; |
| block->cfaFoundConstants = false; |
| for (size_t i = 0; i < block->valuesAtHead.numberOfArguments(); ++i) { |
| block->valuesAtHead.argument(i).clear(); |
| block->valuesAtTail.argument(i).clear(); |
| } |
| for (size_t i = 0; i < block->valuesAtHead.numberOfLocals(); ++i) { |
| block->valuesAtHead.local(i).clear(); |
| block->valuesAtTail.local(i).clear(); |
| } |
| if (!block->isOSRTarget) |
| continue; |
| if (block->bytecodeBegin != graph.m_osrEntryBytecodeIndex) |
| continue; |
| for (size_t i = 0; i < graph.m_mustHandleValues.size(); ++i) { |
| AbstractValue value; |
| value.setMostSpecific(graph.m_mustHandleValues[i]); |
| int operand = graph.m_mustHandleValues.operandForIndex(i); |
| block->valuesAtHead.operand(operand).merge(value); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Initializing Block #%u, operand r%d, to ", blockIndex, operand); |
| block->valuesAtHead.operand(operand).dump(WTF::dataFile()); |
| dataLogF("\n"); |
| #endif |
| } |
| block->cfaShouldRevisit = true; |
| } |
| } |
| |
| bool AbstractState::endBasicBlock(MergeMode mergeMode) |
| { |
| ASSERT(m_block); |
| |
| BasicBlock* block = m_block; // Save the block for successor merging. |
| |
| block->cfaFoundConstants = m_foundConstants; |
| block->cfaDidFinish = m_isValid; |
| block->cfaBranchDirection = m_branchDirection; |
| |
| if (!m_isValid) { |
| reset(); |
| return false; |
| } |
| |
| bool changed = false; |
| |
| if (mergeMode != DontMerge || !ASSERT_DISABLED) { |
| for (size_t argument = 0; argument < block->variablesAtTail.numberOfArguments(); ++argument) { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Merging state for argument %zu.\n", argument); |
| #endif |
| AbstractValue& destination = block->valuesAtTail.argument(argument); |
| changed |= mergeStateAtTail(destination, m_variables.argument(argument), block->variablesAtTail.argument(argument)); |
| } |
| |
| for (size_t local = 0; local < block->variablesAtTail.numberOfLocals(); ++local) { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Merging state for local %zu.\n", local); |
| #endif |
| AbstractValue& destination = block->valuesAtTail.local(local); |
| changed |= mergeStateAtTail(destination, m_variables.local(local), block->variablesAtTail.local(local)); |
| } |
| } |
| |
| ASSERT(mergeMode != DontMerge || !changed); |
| |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Branch direction = %s\n", branchDirectionToString(m_branchDirection)); |
| #endif |
| |
| reset(); |
| |
| if (mergeMode != MergeToSuccessors) |
| return changed; |
| |
| return mergeToSuccessors(m_graph, block); |
| } |
| |
| void AbstractState::reset() |
| { |
| m_block = 0; |
| m_isValid = false; |
| m_branchDirection = InvalidBranchDirection; |
| } |
| |
| AbstractState::BooleanResult AbstractState::booleanResult(Node& node, AbstractValue& value) |
| { |
| JSValue childConst = value.value(); |
| if (childConst) { |
| if (childConst.toBoolean(m_codeBlock->globalObjectFor(node.codeOrigin)->globalExec())) |
| return DefinitelyTrue; |
| return DefinitelyFalse; |
| } |
| |
| // Next check if we can fold because we know that the source is an object or string and does not equal undefined. |
| if (isCellSpeculation(value.m_type) |
| && value.m_currentKnownStructure.hasSingleton()) { |
| Structure* structure = value.m_currentKnownStructure.singleton(); |
| if (!structure->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node.codeOrigin)) |
| && structure->typeInfo().type() != StringType) |
| return DefinitelyTrue; |
| } |
| |
| return UnknownBooleanResult; |
| } |
| |
| bool AbstractState::execute(unsigned indexInBlock) |
| { |
| ASSERT(m_block); |
| ASSERT(m_isValid); |
| |
| m_didClobber = false; |
| |
| NodeIndex nodeIndex = m_block->at(indexInBlock); |
| Node& node = m_graph[nodeIndex]; |
| |
| if (!node.shouldGenerate()) |
| return true; |
| |
| switch (node.op()) { |
| case JSConstant: |
| case WeakJSConstant: |
| case PhantomArguments: { |
| forNode(nodeIndex).set(m_graph.valueOfJSConstant(nodeIndex)); |
| node.setCanExit(false); |
| break; |
| } |
| |
| case Identity: { |
| forNode(nodeIndex) = forNode(node.child1()); |
| node.setCanExit(false); |
| break; |
| } |
| |
| case GetLocal: { |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| if (variableAccessData->prediction() == SpecNone) { |
| m_isValid = false; |
| node.setCanExit(true); |
| break; |
| } |
| bool canExit = false; |
| AbstractValue value = m_variables.operand(variableAccessData->local()); |
| if (!variableAccessData->isCaptured()) { |
| if (value.isClear()) |
| canExit |= true; |
| } |
| if (value.value()) |
| m_foundConstants = true; |
| forNode(nodeIndex) = value; |
| node.setCanExit(canExit); |
| break; |
| } |
| |
| case GetLocalUnlinked: { |
| AbstractValue value = m_variables.operand(node.unlinkedLocal()); |
| if (value.value()) |
| m_foundConstants = true; |
| forNode(nodeIndex) = value; |
| node.setCanExit(false); |
| break; |
| } |
| |
| case SetLocal: { |
| if (node.variableAccessData()->isCaptured() |
| || m_graph.isCreatedThisArgument(node.local())) { |
| m_variables.operand(node.local()) = forNode(node.child1()); |
| node.setCanExit(false); |
| break; |
| } |
| |
| if (node.variableAccessData()->shouldUseDoubleFormat()) { |
| speculateNumberUnary(node); |
| m_variables.operand(node.local()).set(SpecDouble); |
| break; |
| } |
| |
| SpeculatedType predictedType = node.variableAccessData()->argumentAwarePrediction(); |
| if (isInt32Speculation(predictedType)) |
| speculateInt32Unary(node); |
| else if (isCellSpeculation(predictedType)) { |
| node.setCanExit(!isCellSpeculation(forNode(node.child1()).m_type)); |
| forNode(node.child1()).filter(SpecCell); |
| } else if (isBooleanSpeculation(predictedType)) |
| speculateBooleanUnary(node); |
| else |
| node.setCanExit(false); |
| |
| m_variables.operand(node.local()) = forNode(node.child1()); |
| break; |
| } |
| |
| case SetArgument: |
| // Assert that the state of arguments has been set. |
| ASSERT(!m_block->valuesAtHead.operand(node.local()).isClear()); |
| node.setCanExit(false); |
| break; |
| |
| case BitAnd: |
| case BitOr: |
| case BitXor: |
| case BitRShift: |
| case BitLShift: |
| case BitURShift: { |
| JSValue left = forNode(node.child1()).value(); |
| JSValue right = forNode(node.child2()).value(); |
| if (left && right && left.isInt32() && right.isInt32()) { |
| int32_t a = left.asInt32(); |
| int32_t b = right.asInt32(); |
| bool constantWasSet; |
| switch (node.op()) { |
| case BitAnd: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(a & b)); |
| break; |
| case BitOr: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(a | b)); |
| break; |
| case BitXor: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(a ^ b)); |
| break; |
| case BitRShift: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(a >> static_cast<uint32_t>(b))); |
| break; |
| case BitLShift: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(a << static_cast<uint32_t>(b))); |
| break; |
| case BitURShift: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(static_cast<uint32_t>(a) >> static_cast<uint32_t>(b))); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| constantWasSet = false; |
| } |
| if (constantWasSet) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| } |
| speculateInt32Binary(node); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| |
| case UInt32ToNumber: { |
| JSValue child = forNode(node.child1()).value(); |
| if (child && child.isNumber()) { |
| ASSERT(child.isInt32()); |
| if (trySetConstant(nodeIndex, JSValue(child.asUInt32()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| } |
| if (!node.canSpeculateInteger()) { |
| forNode(nodeIndex).set(SpecDouble); |
| node.setCanExit(false); |
| } else { |
| forNode(nodeIndex).set(SpecInt32); |
| node.setCanExit(true); |
| } |
| break; |
| } |
| |
| |
| case DoubleAsInt32: { |
| JSValue child = forNode(node.child1()).value(); |
| if (child && child.isNumber()) { |
| double asDouble = child.asNumber(); |
| int32_t asInt = JSC::toInt32(asDouble); |
| if (bitwise_cast<int64_t>(static_cast<double>(asInt)) == bitwise_cast<int64_t>(asDouble) |
| && trySetConstant(nodeIndex, JSValue(asInt))) { |
| m_foundConstants = true; |
| break; |
| } |
| } |
| node.setCanExit(true); |
| forNode(node.child1()).filter(SpecNumber); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| |
| case ValueToInt32: { |
| JSValue child = forNode(node.child1()).value(); |
| if (child && child.isNumber()) { |
| bool constantWasSet; |
| if (child.isInt32()) |
| constantWasSet = trySetConstant(nodeIndex, child); |
| else |
| constantWasSet = trySetConstant(nodeIndex, JSValue(JSC::toInt32(child.asDouble()))); |
| if (constantWasSet) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| } |
| if (m_graph[node.child1()].shouldSpeculateInteger()) |
| speculateInt32Unary(node); |
| else if (m_graph[node.child1()].shouldSpeculateNumber()) |
| speculateNumberUnary(node); |
| else if (m_graph[node.child1()].shouldSpeculateBoolean()) |
| speculateBooleanUnary(node); |
| else |
| node.setCanExit(false); |
| |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| |
| case Int32ToDouble: { |
| JSValue child = forNode(node.child1()).value(); |
| if (child && child.isNumber() |
| && trySetConstant(nodeIndex, JSValue(JSValue::EncodeAsDouble, child.asNumber()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| speculateNumberUnary(node); |
| if (isInt32Speculation(forNode(node.child1()).m_type)) |
| forNode(nodeIndex).set(SpecDoubleReal); |
| else |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| } |
| |
| case CheckNumber: |
| forNode(node.child1()).filter(SpecNumber); |
| break; |
| |
| case ValueAdd: |
| case ArithAdd: { |
| JSValue left = forNode(node.child1()).value(); |
| JSValue right = forNode(node.child2()).value(); |
| if (left && right && left.isNumber() && right.isNumber() |
| && trySetConstant(nodeIndex, JSValue(left.asNumber() + right.asNumber()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| if (m_graph.addShouldSpeculateInteger(node)) { |
| speculateInt32Binary( |
| node, !nodeCanTruncateInteger(node.arithNodeFlags())); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| if (Node::shouldSpeculateNumberExpectingDefined(m_graph[node.child1()], m_graph[node.child2()])) { |
| speculateNumberBinary(node); |
| if (isRealNumberSpeculation(forNode(node.child1()).m_type) |
| && isRealNumberSpeculation(forNode(node.child2()).m_type)) |
| forNode(nodeIndex).set(SpecDoubleReal); |
| else |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| } |
| if (node.op() == ValueAdd) { |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).set(SpecString | SpecInt32 | SpecNumber); |
| node.setCanExit(false); |
| break; |
| } |
| // We don't handle this yet. :-( |
| m_isValid = false; |
| node.setCanExit(true); |
| break; |
| } |
| |
| case ArithSub: { |
| JSValue left = forNode(node.child1()).value(); |
| JSValue right = forNode(node.child2()).value(); |
| if (left && right && left.isNumber() && right.isNumber() |
| && trySetConstant(nodeIndex, JSValue(left.asNumber() - right.asNumber()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| if (m_graph.addShouldSpeculateInteger(node)) { |
| speculateInt32Binary( |
| node, !nodeCanTruncateInteger(node.arithNodeFlags())); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| speculateNumberBinary(node); |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| } |
| |
| case ArithNegate: { |
| JSValue child = forNode(node.child1()).value(); |
| if (child && child.isNumber() |
| && trySetConstant(nodeIndex, JSValue(-child.asNumber()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| if (m_graph.negateShouldSpeculateInteger(node)) { |
| speculateInt32Unary( |
| node, !nodeCanTruncateInteger(node.arithNodeFlags())); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| speculateNumberUnary(node); |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| } |
| |
| case ArithMul: { |
| JSValue left = forNode(node.child1()).value(); |
| JSValue right = forNode(node.child2()).value(); |
| if (left && right && left.isNumber() && right.isNumber() |
| && trySetConstant(nodeIndex, JSValue(left.asNumber() * right.asNumber()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| if (m_graph.mulShouldSpeculateInteger(node)) { |
| speculateInt32Binary( |
| node, |
| !nodeCanTruncateInteger(node.arithNodeFlags()) |
| || !nodeCanIgnoreNegativeZero(node.arithNodeFlags())); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| speculateNumberBinary(node); |
| if (isRealNumberSpeculation(forNode(node.child1()).m_type) |
| || isRealNumberSpeculation(forNode(node.child2()).m_type)) |
| forNode(nodeIndex).set(SpecDoubleReal); |
| else |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| } |
| |
| case ArithDiv: |
| case ArithMin: |
| case ArithMax: |
| case ArithMod: { |
| JSValue left = forNode(node.child1()).value(); |
| JSValue right = forNode(node.child2()).value(); |
| if (left && right && left.isNumber() && right.isNumber()) { |
| double a = left.asNumber(); |
| double b = right.asNumber(); |
| bool constantWasSet; |
| switch (node.op()) { |
| case ArithDiv: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(a / b)); |
| break; |
| case ArithMin: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(a < b ? a : (b <= a ? b : a + b))); |
| break; |
| case ArithMax: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(a > b ? a : (b >= a ? b : a + b))); |
| break; |
| case ArithMod: |
| constantWasSet = trySetConstant(nodeIndex, JSValue(fmod(a, b))); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| constantWasSet = false; |
| break; |
| } |
| if (constantWasSet) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| } |
| if (Node::shouldSpeculateIntegerForArithmetic( |
| m_graph[node.child1()], m_graph[node.child2()]) |
| && node.canSpeculateInteger()) { |
| speculateInt32Binary(node, true); // forcing can-exit, which is a bit on the conservative side. |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| speculateNumberBinary(node); |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| } |
| |
| case ArithAbs: { |
| JSValue child = forNode(node.child1()).value(); |
| if (child && child.isNumber() |
| && trySetConstant(nodeIndex, JSValue(fabs(child.asNumber())))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| if (m_graph[node.child1()].shouldSpeculateIntegerForArithmetic() |
| && node.canSpeculateInteger()) { |
| speculateInt32Unary(node, true); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| speculateNumberUnary(node); |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| } |
| |
| case ArithSqrt: { |
| JSValue child = forNode(node.child1()).value(); |
| if (child && child.isNumber() |
| && trySetConstant(nodeIndex, JSValue(sqrt(child.asNumber())))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| speculateNumberUnary(node); |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| } |
| |
| case LogicalNot: { |
| bool didSetConstant = false; |
| switch (booleanResult(node, forNode(node.child1()))) { |
| case DefinitelyTrue: |
| didSetConstant = trySetConstant(nodeIndex, jsBoolean(false)); |
| break; |
| case DefinitelyFalse: |
| didSetConstant = trySetConstant(nodeIndex, jsBoolean(true)); |
| break; |
| default: |
| break; |
| } |
| if (didSetConstant) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| Node& child = m_graph[node.child1()]; |
| if (isBooleanSpeculation(child.prediction())) |
| speculateBooleanUnary(node); |
| else if (child.shouldSpeculateNonStringCellOrOther()) { |
| node.setCanExit(true); |
| forNode(node.child1()).filter((SpecCell & ~SpecString) | SpecOther); |
| } else if (child.shouldSpeculateInteger()) |
| speculateInt32Unary(node); |
| else if (child.shouldSpeculateNumber()) |
| speculateNumberUnary(node); |
| else |
| node.setCanExit(false); |
| forNode(nodeIndex).set(SpecBoolean); |
| break; |
| } |
| |
| case IsUndefined: |
| case IsBoolean: |
| case IsNumber: |
| case IsString: |
| case IsObject: |
| case IsFunction: { |
| node.setCanExit(node.op() == IsUndefined && m_codeBlock->globalObjectFor(node.codeOrigin)->masqueradesAsUndefinedWatchpoint()->isStillValid()); |
| JSValue child = forNode(node.child1()).value(); |
| if (child) { |
| bool constantWasSet; |
| switch (node.op()) { |
| case IsUndefined: |
| if (m_codeBlock->globalObjectFor(node.codeOrigin)->masqueradesAsUndefinedWatchpoint()->isStillValid()) { |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean( |
| child.isCell() |
| ? false |
| : child.isUndefined())); |
| } else { |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean( |
| child.isCell() |
| ? child.asCell()->structure()->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node.codeOrigin)) |
| : child.isUndefined())); |
| } |
| break; |
| case IsBoolean: |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(child.isBoolean())); |
| break; |
| case IsNumber: |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(child.isNumber())); |
| break; |
| case IsString: |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(isJSString(child))); |
| break; |
| default: |
| constantWasSet = false; |
| break; |
| } |
| if (constantWasSet) { |
| m_foundConstants = true; |
| break; |
| } |
| } |
| forNode(nodeIndex).set(SpecBoolean); |
| break; |
| } |
| |
| case CompareLess: |
| case CompareLessEq: |
| case CompareGreater: |
| case CompareGreaterEq: |
| case CompareEq: { |
| bool constantWasSet = false; |
| |
| JSValue leftConst = forNode(node.child1()).value(); |
| JSValue rightConst = forNode(node.child2()).value(); |
| if (leftConst && rightConst && leftConst.isNumber() && rightConst.isNumber()) { |
| double a = leftConst.asNumber(); |
| double b = rightConst.asNumber(); |
| switch (node.op()) { |
| case CompareLess: |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(a < b)); |
| break; |
| case CompareLessEq: |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(a <= b)); |
| break; |
| case CompareGreater: |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(a > b)); |
| break; |
| case CompareGreaterEq: |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(a >= b)); |
| break; |
| case CompareEq: |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(a == b)); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| constantWasSet = false; |
| break; |
| } |
| } |
| |
| if (!constantWasSet && node.op() == CompareEq) { |
| SpeculatedType leftType = forNode(node.child1()).m_type; |
| SpeculatedType rightType = forNode(node.child2()).m_type; |
| if ((isInt32Speculation(leftType) && isOtherSpeculation(rightType)) |
| || (isOtherSpeculation(leftType) && isInt32Speculation(rightType))) |
| constantWasSet = trySetConstant(nodeIndex, jsBoolean(false)); |
| } |
| |
| if (constantWasSet) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| |
| forNode(nodeIndex).set(SpecBoolean); |
| |
| Node& left = m_graph[node.child1()]; |
| Node& right = m_graph[node.child2()]; |
| SpeculatedType filter; |
| SpeculatedTypeChecker checker; |
| if (Node::shouldSpeculateInteger(left, right)) { |
| filter = SpecInt32; |
| checker = isInt32Speculation; |
| } else if (Node::shouldSpeculateNumber(left, right)) { |
| filter = SpecNumber; |
| checker = isNumberSpeculation; |
| } else if (node.op() == CompareEq) { |
| if ((m_graph.isConstant(node.child1().index()) |
| && m_graph.valueOfJSConstant(node.child1().index()).isNull()) |
| || (m_graph.isConstant(node.child2().index()) |
| && m_graph.valueOfJSConstant(node.child2().index()).isNull())) { |
| // We can exit if we haven't fired the MasqueradesAsUndefind watchpoint yet. |
| node.setCanExit(m_codeBlock->globalObjectFor(node.codeOrigin)->masqueradesAsUndefinedWatchpoint()->isStillValid()); |
| break; |
| } |
| |
| if (left.shouldSpeculateString() || right.shouldSpeculateString()) { |
| node.setCanExit(false); |
| break; |
| } |
| if (left.shouldSpeculateNonStringCell() && right.shouldSpeculateNonStringCellOrOther()) { |
| node.setCanExit(true); |
| forNode(node.child1()).filter(SpecCell & ~SpecString); |
| forNode(node.child2()).filter((SpecCell & ~SpecString) | SpecOther); |
| break; |
| } |
| if (left.shouldSpeculateNonStringCellOrOther() && right.shouldSpeculateNonStringCell()) { |
| node.setCanExit(true); |
| forNode(node.child1()).filter((SpecCell & ~SpecString) | SpecOther); |
| forNode(node.child2()).filter(SpecCell & ~SpecString); |
| break; |
| } |
| if (left.shouldSpeculateNonStringCell() && right.shouldSpeculateNonStringCell()) { |
| node.setCanExit(true); |
| forNode(node.child1()).filter(SpecCell & ~SpecString); |
| forNode(node.child2()).filter(SpecCell & ~SpecString); |
| break; |
| } |
| |
| filter = SpecTop; |
| checker = isAnySpeculation; |
| clobberWorld(node.codeOrigin, indexInBlock); |
| } else { |
| filter = SpecTop; |
| checker = isAnySpeculation; |
| clobberWorld(node.codeOrigin, indexInBlock); |
| } |
| node.setCanExit( |
| !checker(forNode(node.child1()).m_type) |
| || !checker(forNode(node.child2()).m_type)); |
| forNode(node.child1()).filter(filter); |
| forNode(node.child2()).filter(filter); |
| break; |
| } |
| |
| case CompareStrictEq: { |
| JSValue left = forNode(node.child1()).value(); |
| JSValue right = forNode(node.child2()).value(); |
| if (left && right && left.isNumber() && right.isNumber() |
| && trySetConstant(nodeIndex, jsBoolean(left.asNumber() == right.asNumber()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| forNode(nodeIndex).set(SpecBoolean); |
| if (m_graph.isJSConstant(node.child1().index())) { |
| JSValue value = m_graph.valueOfJSConstant(node.child1().index()); |
| if (!value.isNumber() && !value.isString()) { |
| node.setCanExit(false); |
| break; |
| } |
| } |
| if (m_graph.isJSConstant(node.child2().index())) { |
| JSValue value = m_graph.valueOfJSConstant(node.child2().index()); |
| if (!value.isNumber() && !value.isString()) { |
| node.setCanExit(false); |
| break; |
| } |
| } |
| if (Node::shouldSpeculateInteger( |
| m_graph[node.child1()], m_graph[node.child2()])) { |
| speculateInt32Binary(node); |
| break; |
| } |
| if (Node::shouldSpeculateNumber( |
| m_graph[node.child1()], m_graph[node.child2()])) { |
| speculateNumberBinary(node); |
| break; |
| } |
| Node& leftNode = m_graph[node.child1()]; |
| Node& rightNode = m_graph[node.child2()]; |
| if (leftNode.shouldSpeculateString() || rightNode.shouldSpeculateString()) { |
| node.setCanExit(false); |
| break; |
| } |
| if (leftNode.shouldSpeculateNonStringCell() && rightNode.shouldSpeculateNonStringCell()) { |
| node.setCanExit(true); |
| forNode(node.child1()).filter((SpecCell & ~SpecString) | SpecOther); |
| forNode(node.child2()).filter((SpecCell & ~SpecString) | SpecOther); |
| break; |
| } |
| node.setCanExit(false); |
| break; |
| } |
| |
| case StringCharCodeAt: |
| node.setCanExit(true); |
| forNode(node.child1()).filter(SpecString); |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| |
| case StringCharAt: |
| node.setCanExit(true); |
| forNode(node.child1()).filter(SpecString); |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecString); |
| break; |
| |
| case GetByVal: { |
| node.setCanExit(true); |
| switch (node.arrayMode().type()) { |
| case Array::SelectUsingPredictions: |
| case Array::Unprofiled: |
| case Array::Undecided: |
| ASSERT_NOT_REACHED(); |
| break; |
| case Array::ForceExit: |
| m_isValid = false; |
| break; |
| case Array::Generic: |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).makeTop(); |
| break; |
| case Array::String: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecString); |
| break; |
| case Array::Arguments: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).makeTop(); |
| break; |
| case Array::Int32: |
| forNode(node.child2()).filter(SpecInt32); |
| if (node.arrayMode().isOutOfBounds()) { |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).makeTop(); |
| } else |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| case Array::Double: |
| forNode(node.child2()).filter(SpecInt32); |
| if (node.arrayMode().isOutOfBounds()) { |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).makeTop(); |
| } else if (node.arrayMode().isSaneChain()) |
| forNode(nodeIndex).set(SpecDouble); |
| else |
| forNode(nodeIndex).set(SpecDoubleReal); |
| break; |
| case Array::Contiguous: |
| case Array::ArrayStorage: |
| case Array::SlowPutArrayStorage: |
| forNode(node.child2()).filter(SpecInt32); |
| if (node.arrayMode().isOutOfBounds()) |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).makeTop(); |
| break; |
| case Array::Int8Array: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| case Array::Int16Array: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| case Array::Int32Array: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| case Array::Uint8Array: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| case Array::Uint8ClampedArray: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| case Array::Uint16Array: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| case Array::Uint32Array: |
| forNode(node.child2()).filter(SpecInt32); |
| if (node.shouldSpeculateInteger()) |
| forNode(nodeIndex).set(SpecInt32); |
| else |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| case Array::Float32Array: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| case Array::Float64Array: |
| forNode(node.child2()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecDouble); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| break; |
| } |
| |
| case PutByVal: |
| case PutByValAlias: { |
| node.setCanExit(true); |
| Edge child1 = m_graph.varArgChild(node, 0); |
| Edge child2 = m_graph.varArgChild(node, 1); |
| Edge child3 = m_graph.varArgChild(node, 2); |
| switch (node.arrayMode().modeForPut().type()) { |
| case Array::ForceExit: |
| m_isValid = false; |
| break; |
| case Array::Generic: |
| clobberWorld(node.codeOrigin, indexInBlock); |
| break; |
| case Array::Int32: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| forNode(child3).filter(SpecInt32); |
| if (node.arrayMode().isOutOfBounds()) |
| clobberWorld(node.codeOrigin, indexInBlock); |
| break; |
| case Array::Double: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| forNode(child3).filter(SpecRealNumber); |
| if (node.arrayMode().isOutOfBounds()) |
| clobberWorld(node.codeOrigin, indexInBlock); |
| break; |
| case Array::Contiguous: |
| case Array::ArrayStorage: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (node.arrayMode().isOutOfBounds()) |
| clobberWorld(node.codeOrigin, indexInBlock); |
| break; |
| case Array::SlowPutArrayStorage: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (node.arrayMode().mayStoreToHole()) |
| clobberWorld(node.codeOrigin, indexInBlock); |
| break; |
| case Array::Arguments: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| break; |
| case Array::Int8Array: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (m_graph[child3].shouldSpeculateInteger()) |
| forNode(child3).filter(SpecInt32); |
| else |
| forNode(child3).filter(SpecNumber); |
| break; |
| case Array::Int16Array: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (m_graph[child3].shouldSpeculateInteger()) |
| forNode(child3).filter(SpecInt32); |
| else |
| forNode(child3).filter(SpecNumber); |
| break; |
| case Array::Int32Array: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (m_graph[child3].shouldSpeculateInteger()) |
| forNode(child3).filter(SpecInt32); |
| else |
| forNode(child3).filter(SpecNumber); |
| break; |
| case Array::Uint8Array: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (m_graph[child3].shouldSpeculateInteger()) |
| forNode(child3).filter(SpecInt32); |
| else |
| forNode(child3).filter(SpecNumber); |
| break; |
| case Array::Uint8ClampedArray: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (m_graph[child3].shouldSpeculateInteger()) |
| forNode(child3).filter(SpecInt32); |
| else |
| forNode(child3).filter(SpecNumber); |
| break; |
| case Array::Uint16Array: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (m_graph[child3].shouldSpeculateInteger()) |
| forNode(child3).filter(SpecInt32); |
| else |
| forNode(child3).filter(SpecNumber); |
| break; |
| case Array::Uint32Array: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| if (m_graph[child3].shouldSpeculateInteger()) |
| forNode(child3).filter(SpecInt32); |
| else |
| forNode(child3).filter(SpecNumber); |
| break; |
| case Array::Float32Array: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| forNode(child3).filter(SpecNumber); |
| break; |
| case Array::Float64Array: |
| forNode(child1).filter(SpecCell); |
| forNode(child2).filter(SpecInt32); |
| forNode(child3).filter(SpecNumber); |
| break; |
| default: |
| CRASH(); |
| break; |
| } |
| break; |
| } |
| |
| case ArrayPush: |
| node.setCanExit(true); |
| switch (node.arrayMode().type()) { |
| case Array::Int32: |
| forNode(node.child2()).filter(SpecInt32); |
| break; |
| case Array::Double: |
| forNode(node.child2()).filter(SpecRealNumber); |
| break; |
| default: |
| break; |
| } |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).set(SpecNumber); |
| break; |
| |
| case ArrayPop: |
| node.setCanExit(true); |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case RegExpExec: |
| case RegExpTest: |
| node.setCanExit( |
| !isCellSpeculation(forNode(node.child1()).m_type) |
| || !isCellSpeculation(forNode(node.child2()).m_type)); |
| forNode(node.child1()).filter(SpecCell); |
| forNode(node.child2()).filter(SpecCell); |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case Jump: |
| node.setCanExit(false); |
| break; |
| |
| case Branch: { |
| BooleanResult result = booleanResult(node, forNode(node.child1())); |
| if (result == DefinitelyTrue) { |
| m_branchDirection = TakeTrue; |
| node.setCanExit(false); |
| break; |
| } |
| if (result == DefinitelyFalse) { |
| m_branchDirection = TakeFalse; |
| node.setCanExit(false); |
| break; |
| } |
| // FIXME: The above handles the trivial cases of sparse conditional |
| // constant propagation, but we can do better: |
| // We can specialize the source variable's value on each direction of |
| // the branch. |
| Node& child = m_graph[node.child1()]; |
| if (child.shouldSpeculateBoolean()) |
| speculateBooleanUnary(node); |
| else if (child.shouldSpeculateNonStringCellOrOther()) { |
| node.setCanExit(true); |
| forNode(node.child1()).filter((SpecCell & ~SpecString) | SpecOther); |
| } else if (child.shouldSpeculateInteger()) |
| speculateInt32Unary(node); |
| else if (child.shouldSpeculateNumber()) |
| speculateNumberUnary(node); |
| else |
| node.setCanExit(false); |
| m_branchDirection = TakeBoth; |
| break; |
| } |
| |
| case Return: |
| m_isValid = false; |
| node.setCanExit(false); |
| break; |
| |
| case Throw: |
| case ThrowReferenceError: |
| m_isValid = false; |
| node.setCanExit(true); |
| break; |
| |
| case ToPrimitive: { |
| JSValue childConst = forNode(node.child1()).value(); |
| if (childConst && childConst.isNumber() && trySetConstant(nodeIndex, childConst)) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| |
| Node& child = m_graph[node.child1()]; |
| if (child.shouldSpeculateInteger()) { |
| speculateInt32Unary(node); |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| } |
| |
| AbstractValue& source = forNode(node.child1()); |
| AbstractValue& destination = forNode(nodeIndex); |
| |
| SpeculatedType type = source.m_type; |
| if (type & ~(SpecNumber | SpecString | SpecBoolean)) { |
| type &= (SpecNumber | SpecString | SpecBoolean); |
| type |= SpecString; |
| } |
| destination.set(type); |
| node.setCanExit(false); |
| break; |
| } |
| |
| case StrCat: |
| node.setCanExit(false); |
| forNode(nodeIndex).set(SpecString); |
| break; |
| |
| case NewArray: |
| node.setCanExit(true); |
| forNode(nodeIndex).set(m_graph.globalObjectFor(node.codeOrigin)->arrayStructureForIndexingTypeDuringAllocation(node.indexingType())); |
| m_haveStructures = true; |
| break; |
| |
| case NewArrayBuffer: |
| node.setCanExit(true); |
| forNode(nodeIndex).set(m_graph.globalObjectFor(node.codeOrigin)->arrayStructureForIndexingTypeDuringAllocation(node.indexingType())); |
| m_haveStructures = true; |
| break; |
| |
| case NewArrayWithSize: |
| node.setCanExit(true); |
| forNode(node.child1()).filter(SpecInt32); |
| forNode(nodeIndex).set(SpecArray); |
| m_haveStructures = true; |
| break; |
| |
| case NewRegexp: |
| node.setCanExit(false); |
| forNode(nodeIndex).set(m_graph.globalObjectFor(node.codeOrigin)->regExpStructure()); |
| m_haveStructures = true; |
| break; |
| |
| case ConvertThis: { |
| Node& child = m_graph[node.child1()]; |
| AbstractValue& source = forNode(node.child1()); |
| AbstractValue& destination = forNode(nodeIndex); |
| |
| if (isObjectSpeculation(source.m_type)) { |
| // This is the simple case. We already know that the source is an |
| // object, so there's nothing to do. I don't think this case will |
| // be hit, but then again, you never know. |
| destination = source; |
| node.setCanExit(false); |
| m_foundConstants = true; // Tell the constant folder to turn this into Identity. |
| break; |
| } |
| |
| node.setCanExit(true); |
| |
| if (isOtherSpeculation(child.prediction())) { |
| source.filter(SpecOther); |
| destination.set(SpecObjectOther); |
| break; |
| } |
| |
| if (isObjectSpeculation(child.prediction())) { |
| source.filter(SpecObjectMask); |
| destination = source; |
| break; |
| } |
| |
| destination = source; |
| destination.merge(SpecObjectOther); |
| break; |
| } |
| |
| case CreateThis: { |
| AbstractValue& source = forNode(node.child1()); |
| AbstractValue& destination = forNode(nodeIndex); |
| |
| node.setCanExit(!isCellSpeculation(source.m_type)); |
| |
| source.filter(SpecFunction); |
| destination.set(SpecFinalObject); |
| break; |
| } |
| |
| case InheritorIDWatchpoint: |
| node.setCanExit(true); |
| break; |
| |
| case NewObject: |
| node.setCanExit(false); |
| forNode(nodeIndex).set(node.structure()); |
| m_haveStructures = true; |
| break; |
| |
| case CreateActivation: |
| node.setCanExit(false); |
| forNode(nodeIndex).set(m_codeBlock->globalObjectFor(node.codeOrigin)->activationStructure()); |
| m_haveStructures = true; |
| break; |
| |
| case CreateArguments: |
| node.setCanExit(false); |
| forNode(nodeIndex).set(m_codeBlock->globalObjectFor(node.codeOrigin)->argumentsStructure()); |
| m_haveStructures = true; |
| break; |
| |
| case TearOffActivation: |
| case TearOffArguments: |
| node.setCanExit(false); |
| // Does nothing that is user-visible. |
| break; |
| |
| case CheckArgumentsNotCreated: |
| if (isEmptySpeculation( |
| m_variables.operand( |
| m_graph.argumentsRegisterFor(node.codeOrigin)).m_type)) { |
| node.setCanExit(false); |
| m_foundConstants = true; |
| } else |
| node.setCanExit(true); |
| break; |
| |
| case GetMyArgumentsLength: |
| // We know that this executable does not escape its arguments, so we can optimize |
| // the arguments a bit. Note that this is not sufficient to force constant folding |
| // of GetMyArgumentsLength, because GetMyArgumentsLength is a clobbering operation. |
| // We perform further optimizations on this later on. |
| if (node.codeOrigin.inlineCallFrame) |
| forNode(nodeIndex).set(jsNumber(node.codeOrigin.inlineCallFrame->arguments.size() - 1)); |
| else |
| forNode(nodeIndex).set(SpecInt32); |
| node.setCanExit( |
| !isEmptySpeculation( |
| m_variables.operand( |
| m_graph.argumentsRegisterFor(node.codeOrigin)).m_type)); |
| break; |
| |
| case GetMyArgumentsLengthSafe: |
| node.setCanExit(false); |
| // This potentially clobbers all structures if the arguments object had a getter |
| // installed on the length property. |
| clobberWorld(node.codeOrigin, indexInBlock); |
| // We currently make no guarantee about what this returns because it does not |
| // speculate that the length property is actually a length. |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case GetMyArgumentByVal: |
| node.setCanExit(true); |
| // We know that this executable does not escape its arguments, so we can optimize |
| // the arguments a bit. Note that this ends up being further optimized by the |
| // ArgumentsSimplificationPhase. |
| forNode(node.child1()).filter(SpecInt32); |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case GetMyArgumentByValSafe: |
| node.setCanExit(true); |
| // This potentially clobbers all structures if the property we're accessing has |
| // a getter. We don't speculate against this. |
| clobberWorld(node.codeOrigin, indexInBlock); |
| // But we do speculate that the index is an integer. |
| forNode(node.child1()).filter(SpecInt32); |
| // And the result is unknown. |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case NewFunction: |
| case NewFunctionExpression: |
| case NewFunctionNoCheck: |
| node.setCanExit(false); |
| forNode(nodeIndex).set(m_codeBlock->globalObjectFor(node.codeOrigin)->functionStructure()); |
| break; |
| |
| case GetCallee: |
| node.setCanExit(false); |
| forNode(nodeIndex).set(SpecFunction); |
| break; |
| |
| case GetMyScope: |
| case SkipTopScope: |
| node.setCanExit(false); |
| forNode(nodeIndex).set(SpecCellOther); |
| break; |
| |
| case SkipScope: { |
| node.setCanExit(false); |
| JSValue child = forNode(node.child1()).value(); |
| if (child && trySetConstant(nodeIndex, JSValue(jsCast<JSScope*>(child.asCell())->next()))) { |
| m_foundConstants = true; |
| break; |
| } |
| forNode(nodeIndex).set(SpecCellOther); |
| break; |
| } |
| |
| case GetScopeRegisters: |
| node.setCanExit(false); |
| forNode(node.child1()).filter(SpecCell); |
| forNode(nodeIndex).clear(); // The result is not a JS value. |
| break; |
| |
| case GetScopedVar: |
| node.setCanExit(false); |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case PutScopedVar: |
| node.setCanExit(false); |
| clobberCapturedVars(node.codeOrigin); |
| break; |
| |
| case GetById: |
| case GetByIdFlush: |
| node.setCanExit(true); |
| if (!node.prediction()) { |
| m_isValid = false; |
| break; |
| } |
| if (isCellSpeculation(m_graph[node.child1()].prediction())) { |
| forNode(node.child1()).filter(SpecCell); |
| |
| if (Structure* structure = forNode(node.child1()).bestProvenStructure()) { |
| GetByIdStatus status = GetByIdStatus::computeFor( |
| m_graph.m_globalData, structure, |
| m_graph.m_codeBlock->identifier(node.identifierNumber())); |
| if (status.isSimple()) { |
| // Assert things that we can't handle and that the computeFor() method |
| // above won't be able to return. |
| ASSERT(status.structureSet().size() == 1); |
| ASSERT(status.chain().isEmpty()); |
| |
| if (status.specificValue()) |
| forNode(nodeIndex).set(status.specificValue()); |
| else |
| forNode(nodeIndex).makeTop(); |
| forNode(node.child1()).filter(status.structureSet()); |
| |
| m_foundConstants = true; |
| break; |
| } |
| } |
| } |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case GetArrayLength: |
| node.setCanExit(true); // Lies, but it's true for the common case of JSArray, so it's good enough. |
| forNode(nodeIndex).set(SpecInt32); |
| break; |
| |
| case CheckStructure: |
| case ForwardCheckStructure: { |
| // FIXME: We should be able to propagate the structure sets of constants (i.e. prototypes). |
| AbstractValue& value = forNode(node.child1()); |
| // If this structure check is attempting to prove knowledge already held in |
| // the futurePossibleStructure set then the constant folding phase should |
| // turn this into a watchpoint instead. |
| StructureSet& set = node.structureSet(); |
| if (value.m_futurePossibleStructure.isSubsetOf(set) |
| || value.m_currentKnownStructure.isSubsetOf(set)) |
| m_foundConstants = true; |
| node.setCanExit( |
| !value.m_currentKnownStructure.isSubsetOf(set) |
| || !isCellSpeculation(value.m_type)); |
| value.filter(set); |
| m_haveStructures = true; |
| break; |
| } |
| |
| case StructureTransitionWatchpoint: |
| case ForwardStructureTransitionWatchpoint: { |
| AbstractValue& value = forNode(node.child1()); |
| |
| // It's only valid to issue a structure transition watchpoint if we already |
| // know that the watchpoint covers a superset of the structures known to |
| // belong to the set of future structures that this value may have. |
| // Currently, we only issue singleton watchpoints (that check one structure) |
| // and our futurePossibleStructure set can only contain zero, one, or an |
| // infinity of structures. |
| ASSERT(value.m_futurePossibleStructure.isSubsetOf(StructureSet(node.structure()))); |
| |
| ASSERT(value.isClear() || isCellSpeculation(value.m_type)); // Value could be clear if we've proven must-exit due to a speculation statically known to be bad. |
| value.filter(node.structure()); |
| m_haveStructures = true; |
| node.setCanExit(true); |
| break; |
| } |
| |
| case PutStructure: |
| case PhantomPutStructure: |
| node.setCanExit(false); |
| if (!forNode(node.child1()).m_currentKnownStructure.isClear()) { |
| clobberStructures(indexInBlock); |
| forNode(node.child1()).set(node.structureTransitionData().newStructure); |
| m_haveStructures = true; |
| } |
| break; |
| case GetButterfly: |
| case AllocatePropertyStorage: |
| case ReallocatePropertyStorage: |
| node.setCanExit(!isCellSpeculation(forNode(node.child1()).m_type)); |
| forNode(node.child1()).filter(SpecCell); |
| forNode(nodeIndex).clear(); // The result is not a JS value. |
| break; |
| case CheckArray: { |
| if (node.arrayMode().alreadyChecked(m_graph, node, forNode(node.child1()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| node.setCanExit(true); // Lies, but this is followed by operations (like GetByVal) that always exit, so there is no point in us trying to be clever here. |
| switch (node.arrayMode().type()) { |
| case Array::String: |
| forNode(node.child1()).filter(SpecString); |
| break; |
| case Array::Int32: |
| case Array::Double: |
| case Array::Contiguous: |
| case Array::ArrayStorage: |
| case Array::SlowPutArrayStorage: |
| forNode(node.child1()).filter(SpecCell); |
| break; |
| case Array::Arguments: |
| forNode(node.child1()).filter(SpecArguments); |
| break; |
| case Array::Int8Array: |
| forNode(node.child1()).filter(SpecInt8Array); |
| break; |
| case Array::Int16Array: |
| forNode(node.child1()).filter(SpecInt16Array); |
| break; |
| case Array::Int32Array: |
| forNode(node.child1()).filter(SpecInt32Array); |
| break; |
| case Array::Uint8Array: |
| forNode(node.child1()).filter(SpecUint8Array); |
| break; |
| case Array::Uint8ClampedArray: |
| forNode(node.child1()).filter(SpecUint8ClampedArray); |
| break; |
| case Array::Uint16Array: |
| forNode(node.child1()).filter(SpecUint16Array); |
| break; |
| case Array::Uint32Array: |
| forNode(node.child1()).filter(SpecUint32Array); |
| break; |
| case Array::Float32Array: |
| forNode(node.child1()).filter(SpecFloat32Array); |
| break; |
| case Array::Float64Array: |
| forNode(node.child1()).filter(SpecFloat64Array); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| forNode(node.child1()).filterArrayModes(node.arrayMode().arrayModesThatPassFiltering()); |
| m_haveStructures = true; |
| break; |
| } |
| case Arrayify: { |
| if (node.arrayMode().alreadyChecked(m_graph, node, forNode(node.child1()))) { |
| m_foundConstants = true; |
| node.setCanExit(false); |
| break; |
| } |
| ASSERT(node.arrayMode().conversion() == Array::Convert |
| || node.arrayMode().conversion() == Array::RageConvert); |
| node.setCanExit(true); |
| forNode(node.child1()).filter(SpecCell); |
| if (node.child2()) |
| forNode(node.child2()).filter(SpecInt32); |
| clobberStructures(indexInBlock); |
| forNode(node.child1()).filterArrayModes(node.arrayMode().arrayModesThatPassFiltering()); |
| m_haveStructures = true; |
| break; |
| } |
| case ArrayifyToStructure: { |
| AbstractValue& value = forNode(node.child1()); |
| StructureSet set = node.structure(); |
| if (value.m_futurePossibleStructure.isSubsetOf(set) |
| || value.m_currentKnownStructure.isSubsetOf(set)) |
| m_foundConstants = true; |
| node.setCanExit(true); |
| if (node.child2()) |
| forNode(node.child2()).filter(SpecInt32); |
| clobberStructures(indexInBlock); |
| value.filter(set); |
| m_haveStructures = true; |
| break; |
| } |
| case GetIndexedPropertyStorage: { |
| node.setCanExit(false); |
| forNode(nodeIndex).clear(); |
| break; |
| } |
| case GetByOffset: |
| if (!m_graph[node.child1()].hasStorageResult()) { |
| node.setCanExit(!isCellSpeculation(forNode(node.child1()).m_type)); |
| forNode(node.child1()).filter(SpecCell); |
| } |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case PutByOffset: { |
| bool canExit = false; |
| if (!m_graph[node.child1()].hasStorageResult()) { |
| canExit |= !isCellSpeculation(forNode(node.child1()).m_type); |
| forNode(node.child1()).filter(SpecCell); |
| } |
| canExit |= !isCellSpeculation(forNode(node.child2()).m_type); |
| forNode(node.child2()).filter(SpecCell); |
| node.setCanExit(canExit); |
| break; |
| } |
| |
| case CheckFunction: { |
| JSValue value = forNode(node.child1()).value(); |
| if (value == node.function()) { |
| m_foundConstants = true; |
| ASSERT(value); |
| node.setCanExit(false); |
| break; |
| } |
| |
| node.setCanExit(true); // Lies! We can do better. |
| if (!forNode(node.child1()).filterByValue(node.function())) { |
| m_isValid = false; |
| break; |
| } |
| break; |
| } |
| |
| case PutById: |
| case PutByIdDirect: |
| node.setCanExit(true); |
| if (Structure* structure = forNode(node.child1()).bestProvenStructure()) { |
| PutByIdStatus status = PutByIdStatus::computeFor( |
| m_graph.m_globalData, |
| m_graph.globalObjectFor(node.codeOrigin), |
| structure, |
| m_graph.m_codeBlock->identifier(node.identifierNumber()), |
| node.op() == PutByIdDirect); |
| if (status.isSimpleReplace()) { |
| forNode(node.child1()).filter(structure); |
| m_foundConstants = true; |
| break; |
| } |
| if (status.isSimpleTransition()) { |
| clobberStructures(indexInBlock); |
| forNode(node.child1()).set(status.newStructure()); |
| m_haveStructures = true; |
| m_foundConstants = true; |
| break; |
| } |
| } |
| forNode(node.child1()).filter(SpecCell); |
| clobberWorld(node.codeOrigin, indexInBlock); |
| break; |
| |
| case GetGlobalVar: |
| node.setCanExit(false); |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case GlobalVarWatchpoint: |
| node.setCanExit(true); |
| break; |
| |
| case PutGlobalVar: |
| case PutGlobalVarCheck: |
| node.setCanExit(false); |
| break; |
| |
| case CheckHasInstance: |
| node.setCanExit(true); |
| forNode(node.child1()).filter(SpecCell); |
| // Sadly, we don't propagate the fact that we've done CheckHasInstance |
| break; |
| |
| case InstanceOf: |
| node.setCanExit(true); |
| // Again, sadly, we don't propagate the fact that we've done InstanceOf |
| if (!(m_graph[node.child1()].prediction() & ~SpecCell) && !(forNode(node.child1()).m_type & ~SpecCell)) |
| forNode(node.child1()).filter(SpecCell); |
| forNode(node.child2()).filter(SpecCell); |
| forNode(nodeIndex).set(SpecBoolean); |
| break; |
| |
| case Phi: |
| case Flush: |
| node.setCanExit(false); |
| break; |
| |
| case Breakpoint: |
| node.setCanExit(false); |
| break; |
| |
| case Call: |
| case Construct: |
| case Resolve: |
| case ResolveBase: |
| case ResolveBaseStrictPut: |
| case ResolveGlobal: |
| node.setCanExit(true); |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case GarbageValue: |
| clobberWorld(node.codeOrigin, indexInBlock); |
| forNode(nodeIndex).makeTop(); |
| break; |
| |
| case ForceOSRExit: |
| node.setCanExit(true); |
| m_isValid = false; |
| break; |
| |
| case Phantom: |
| case InlineStart: |
| case Nop: |
| case CountExecution: |
| node.setCanExit(false); |
| break; |
| |
| case LastNodeType: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| return m_isValid; |
| } |
| |
| inline void AbstractState::clobberWorld(const CodeOrigin& codeOrigin, unsigned indexInBlock) |
| { |
| clobberCapturedVars(codeOrigin); |
| clobberStructures(indexInBlock); |
| } |
| |
| inline void AbstractState::clobberCapturedVars(const CodeOrigin& codeOrigin) |
| { |
| if (codeOrigin.inlineCallFrame) { |
| const BitVector& capturedVars = codeOrigin.inlineCallFrame->capturedVars; |
| for (size_t i = capturedVars.size(); i--;) { |
| if (!capturedVars.quickGet(i)) |
| continue; |
| m_variables.local(i).makeTop(); |
| } |
| } else { |
| for (size_t i = m_codeBlock->m_numVars; i--;) { |
| if (m_codeBlock->isCaptured(i)) |
| m_variables.local(i).makeTop(); |
| } |
| } |
| |
| for (size_t i = m_variables.numberOfArguments(); i--;) { |
| if (m_codeBlock->isCaptured(argumentToOperand(i))) |
| m_variables.argument(i).makeTop(); |
| } |
| } |
| |
| inline void AbstractState::clobberStructures(unsigned indexInBlock) |
| { |
| if (!m_haveStructures) |
| return; |
| for (size_t i = indexInBlock + 1; i--;) |
| forNode(m_block->at(i)).clobberStructures(); |
| for (size_t i = m_variables.numberOfArguments(); i--;) |
| m_variables.argument(i).clobberStructures(); |
| for (size_t i = m_variables.numberOfLocals(); i--;) |
| m_variables.local(i).clobberStructures(); |
| m_haveStructures = false; |
| m_didClobber = true; |
| } |
| |
| inline bool AbstractState::mergeStateAtTail(AbstractValue& destination, AbstractValue& inVariable, NodeIndex nodeIndex) |
| { |
| if (nodeIndex == NoNode) |
| return false; |
| |
| AbstractValue source; |
| |
| Node& node = m_graph[nodeIndex]; |
| if (!node.refCount()) |
| return false; |
| |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" It's live, node @%u.\n", nodeIndex); |
| #endif |
| |
| if (node.variableAccessData()->isCaptured()) { |
| source = inVariable; |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Transfering "); |
| source.dump(WTF::dataFile()); |
| dataLogF(" from last access due to captured variable.\n"); |
| #endif |
| } else { |
| switch (node.op()) { |
| case Phi: |
| case SetArgument: |
| case Flush: |
| // The block transfers the value from head to tail. |
| source = inVariable; |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Transfering "); |
| source.dump(WTF::dataFile()); |
| dataLogF(" from head to tail.\n"); |
| #endif |
| break; |
| |
| case GetLocal: |
| // The block refines the value with additional speculations. |
| source = forNode(nodeIndex); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Refining to "); |
| source.dump(WTF::dataFile()); |
| dataLogF("\n"); |
| #endif |
| break; |
| |
| case SetLocal: |
| // The block sets the variable, and potentially refines it, both |
| // before and after setting it. |
| if (node.variableAccessData()->shouldUseDoubleFormat()) { |
| // FIXME: This unnecessarily loses precision. |
| source.set(SpecDouble); |
| } else |
| source = forNode(node.child1()); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Setting to "); |
| source.dump(WTF::dataFile()); |
| dataLogF("\n"); |
| #endif |
| break; |
| |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| } |
| |
| if (destination == source) { |
| // Abstract execution did not change the output value of the variable, for this |
| // basic block, on this iteration. |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Not changed!\n"); |
| #endif |
| return false; |
| } |
| |
| // Abstract execution reached a new conclusion about the speculations reached about |
| // this variable after execution of this basic block. Update the state, and return |
| // true to indicate that the fixpoint must go on! |
| destination = source; |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Changed!\n"); |
| #endif |
| return true; |
| } |
| |
| inline bool AbstractState::merge(BasicBlock* from, BasicBlock* to) |
| { |
| ASSERT(from->variablesAtTail.numberOfArguments() == to->variablesAtHead.numberOfArguments()); |
| ASSERT(from->variablesAtTail.numberOfLocals() == to->variablesAtHead.numberOfLocals()); |
| |
| bool changed = false; |
| |
| for (size_t argument = 0; argument < from->variablesAtTail.numberOfArguments(); ++argument) { |
| AbstractValue& destination = to->valuesAtHead.argument(argument); |
| changed |= mergeVariableBetweenBlocks(destination, from->valuesAtTail.argument(argument), to->variablesAtHead.argument(argument), from->variablesAtTail.argument(argument)); |
| } |
| |
| for (size_t local = 0; local < from->variablesAtTail.numberOfLocals(); ++local) { |
| AbstractValue& destination = to->valuesAtHead.local(local); |
| changed |= mergeVariableBetweenBlocks(destination, from->valuesAtTail.local(local), to->variablesAtHead.local(local), from->variablesAtTail.local(local)); |
| } |
| |
| if (!to->cfaHasVisited) |
| changed = true; |
| |
| to->cfaShouldRevisit |= changed; |
| |
| return changed; |
| } |
| |
| inline bool AbstractState::mergeToSuccessors( |
| Graph& graph, BasicBlock* basicBlock) |
| { |
| Node& terminal = graph[basicBlock->last()]; |
| |
| ASSERT(terminal.isTerminal()); |
| |
| switch (terminal.op()) { |
| case Jump: { |
| ASSERT(basicBlock->cfaBranchDirection == InvalidBranchDirection); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Merging to block #%u.\n", terminal.takenBlockIndex()); |
| #endif |
| return merge(basicBlock, graph.m_blocks[terminal.takenBlockIndex()].get()); |
| } |
| |
| case Branch: { |
| ASSERT(basicBlock->cfaBranchDirection != InvalidBranchDirection); |
| bool changed = false; |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Merging to block #%u.\n", terminal.takenBlockIndex()); |
| #endif |
| if (basicBlock->cfaBranchDirection != TakeFalse) |
| changed |= merge(basicBlock, graph.m_blocks[terminal.takenBlockIndex()].get()); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Merging to block #%u.\n", terminal.notTakenBlockIndex()); |
| #endif |
| if (basicBlock->cfaBranchDirection != TakeTrue) |
| changed |= merge(basicBlock, graph.m_blocks[terminal.notTakenBlockIndex()].get()); |
| return changed; |
| } |
| |
| case Return: |
| case Throw: |
| case ThrowReferenceError: |
| ASSERT(basicBlock->cfaBranchDirection == InvalidBranchDirection); |
| return false; |
| |
| default: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| } |
| |
| inline bool AbstractState::mergeVariableBetweenBlocks(AbstractValue& destination, AbstractValue& source, NodeIndex destinationNodeIndex, NodeIndex sourceNodeIndex) |
| { |
| if (destinationNodeIndex == NoNode) |
| return false; |
| |
| ASSERT_UNUSED(sourceNodeIndex, sourceNodeIndex != NoNode); |
| |
| // FIXME: We could do some sparse conditional propagation here! |
| |
| return destination.merge(source); |
| } |
| |
| void AbstractState::dump(PrintStream& out) |
| { |
| bool first = true; |
| for (size_t i = 0; i < m_block->size(); ++i) { |
| NodeIndex index = m_block->at(i); |
| AbstractValue& value = m_nodes[index]; |
| if (value.isClear()) |
| continue; |
| if (first) |
| first = false; |
| else |
| out.printf(" "); |
| out.printf("@%lu:", static_cast<unsigned long>(index)); |
| value.dump(out); |
| } |
| } |
| |
| } } // namespace JSC::DFG |
| |
| #endif // ENABLE(DFG_JIT) |
| |