/* * 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(b))); break; case BitLShift: constantWasSet = trySetConstant(nodeIndex, JSValue(a << static_cast(b))); break; case BitURShift: constantWasSet = trySetConstant(nodeIndex, JSValue(static_cast(a) >> static_cast(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(static_cast(asInt)) == bitwise_cast(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 GetScope: node.setCanExit(false); 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.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); clobberStructures(indexInBlock); value.filter(set); m_haveStructures = true; break; } case GetIndexedPropertyStorage: { switch (node.arrayMode().type()) { case Array::String: // Strings are weird - we may spec fail if the string was a rope. That is of course // stupid, and we should fix that, but for now let's at least be honest about it. node.setCanExit(true); break; default: node.setCanExit(false); break; } 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: 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(index)); value.dump(out); } } } } // namespace JSC::DFG #endif // ENABLE(DFG_JIT)