/*
- * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#include "DFGAbstractInterpreter.h"
#include "GetByIdStatus.h"
+#include "GetterSetter.h"
+#include "JITOperations.h"
+#include "MathCommon.h"
#include "Operations.h"
#include "PutByIdStatus.h"
#include "StringObject.h"
, m_graph(graph)
, m_state(state)
{
+ if (m_graph.m_form == SSA)
+ m_phiChildren = std::make_unique<PhiChildren>(m_graph);
}
template<typename AbstractStateType>
}
// 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->origin.semantic))
- && structure->typeInfo().type() != StringType)
+ if (isCellSpeculation(value.m_type) && !value.m_structure.isTop()) {
+ bool allTrue = true;
+ for (unsigned i = value.m_structure.size(); i--;) {
+ Structure* structure = value.m_structure[i];
+ if (structure->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node->origin.semantic))
+ || structure->typeInfo().type() == StringType) {
+ allTrue = false;
+ break;
+ }
+ }
+ if (allTrue)
return DefinitelyTrue;
}
}
template<typename AbstractStateType>
-bool AbstractInterpreter<AbstractStateType>::startExecuting(Node* node)
+void AbstractInterpreter<AbstractStateType>::startExecuting()
{
ASSERT(m_state.block());
ASSERT(m_state.isValid());
m_state.setDidClobber(false);
-
- node->setCanExit(false);
-
- return node->shouldGenerate();
-}
-
-template<typename AbstractStateType>
-bool AbstractInterpreter<AbstractStateType>::startExecuting(unsigned indexInBlock)
-{
- return startExecuting(m_state.block()->at(indexInBlock));
}
template<typename AbstractStateType>
}
template<typename AbstractStateType>
-void AbstractInterpreter<AbstractStateType>::verifyEdge(Node*, Edge edge)
+void AbstractInterpreter<AbstractStateType>::verifyEdge(Node* node, Edge edge)
{
- RELEASE_ASSERT(!(forNode(edge).m_type & ~typeFilterFor(edge.useKind())));
+ if (!(forNode(edge).m_type & ~typeFilterFor(edge.useKind())))
+ return;
+
+ DFG_CRASH(m_graph, node, toCString("Edge verification error: ", node, "->", edge, " was expected to have type ", SpeculationDump(typeFilterFor(edge.useKind())), " but has type ", SpeculationDump(forNode(edge).m_type), " (", forNode(edge).m_type, ")").data());
}
template<typename AbstractStateType>
switch (node->op()) {
case JSConstant:
case DoubleConstant:
- case Int52Constant:
- case WeakJSConstant:
- case PhantomArguments: {
- setBuiltInConstant(node, m_graph.valueOfJSConstant(node));
+ case Int52Constant: {
+ setBuiltInConstant(node, *node->constant());
break;
}
case Identity: {
forNode(node) = forNode(node->child1());
- break;
- }
-
- case GetArgument: {
- ASSERT(m_graph.m_form == SSA);
- VariableAccessData* variable = node->variableAccessData();
- AbstractValue& value = m_state.variables().operand(variable->local().offset());
- ASSERT(value.isHeapTop());
- FiltrationResult result =
- value.filter(typeFilterFor(useKindFor(variable->flushFormat())));
- ASSERT_UNUSED(result, result == FiltrationOK);
- forNode(node) = value;
+ if (forNode(node).value())
+ m_state.setFoundConstants(true);
break;
}
case ExtractOSREntryLocal: {
- if (!(node->unlinkedLocal().isArgument())
- && m_graph.m_lazyVars.get(node->unlinkedLocal().toLocal())) {
- // This is kind of pessimistic - we could know in some cases that the
- // DFG code at the point of the OSR had already initialized the lazy
- // variable. But maybe this is fine, since we're inserting OSR
- // entrypoints very early in the pipeline - so any lazy initializations
- // ought to be hoisted out anyway.
- forNode(node).makeBytecodeTop();
- } else
- forNode(node).makeHeapTop();
+ forNode(node).makeBytecodeTop();
break;
}
case GetLocal: {
VariableAccessData* variableAccessData = node->variableAccessData();
AbstractValue value = m_state.variables().operand(variableAccessData->local().offset());
- if (!variableAccessData->isCaptured()) {
- if (value.isClear())
- node->setCanExit(true);
- }
+ // The value in the local should already be checked.
+ DFG_ASSERT(m_graph, node, value.isType(typeFilterFor(variableAccessData->flushFormat())));
+ if (value.value())
+ m_state.setFoundConstants(true);
+ forNode(node) = value;
+ break;
+ }
+
+ case GetStack: {
+ StackAccessData* data = node->stackAccessData();
+ AbstractValue value = m_state.variables().operand(data->local);
+ // The value in the local should already be checked.
+ DFG_ASSERT(m_graph, node, value.isType(typeFilterFor(data->format)));
if (value.value())
m_state.setFoundConstants(true);
forNode(node) = value;
}
case SetLocal: {
- m_state.variables().operand(node->local().offset()) = forNode(node->child1());
+ m_state.variables().operand(node->local()) = forNode(node->child1());
+ break;
+ }
+
+ case PutStack: {
+ m_state.variables().operand(node->stackAccessData()->local) = forNode(node->child1());
break;
}
break;
}
+ case KillStack: {
+ // This is just a hint telling us that the OSR state of the local is no longer inside the
+ // flushed data.
+ break;
+ }
+
case SetArgument:
- // Assert that the state of arguments has been set.
- ASSERT(!m_state.block()->valuesAtHead.operand(node->local()).isClear());
+ // Assert that the state of arguments has been set. SetArgument means that someone set
+ // the argument values out-of-band, and currently this always means setting to a
+ // non-clear value.
+ ASSERT(!m_state.variables().operand(node->local()).isClear());
break;
+
+ case LoadVarargs:
+ case ForwardVarargs: {
+ // FIXME: ForwardVarargs should check if the count becomes known, and if it does, it should turn
+ // itself into a straight-line sequence of GetStack/PutStack.
+ // https://bugs.webkit.org/show_bug.cgi?id=143071
+ clobberWorld(node->origin.semantic, clobberLimit);
+ LoadVarargsData* data = node->loadVarargsData();
+ m_state.variables().operand(data->count).setType(SpecInt32);
+ for (unsigned i = data->limit - 1; i--;)
+ m_state.variables().operand(data->start.offset() + i).makeHeapTop();
+ break;
+ }
case BitAnd:
case BitOr:
}
break;
}
+
+ if (node->op() == BitAnd
+ && (isBoolInt32Speculation(forNode(node->child1()).m_type) ||
+ isBoolInt32Speculation(forNode(node->child2()).m_type))) {
+ forNode(node).setType(SpecBoolInt32);
+ break;
+ }
+
forNode(node).setType(SpecInt32);
break;
}
}
}
forNode(node).setType(SpecInt32);
- node->setCanExit(true);
break;
}
if (concreteValue.isBoolean())
setConstant(node, jsNumber(concreteValue.asBoolean()));
else
- setConstant(node, concreteValue);
+ setConstant(node, *m_graph.freeze(concreteValue));
break;
}
AbstractValue& value = forNode(node);
if (node->child1().useKind() == UntypedUse && !(value.m_type & ~SpecBoolean))
m_state.setFoundConstants(true);
if (value.m_type & SpecBoolean) {
- value.merge(SpecInt32);
+ value.merge(SpecBoolInt32);
value.filter(~SpecBoolean);
}
break;
break;
}
}
- node->setCanExit(true);
forNode(node).setType(SpecInt32);
break;
}
}
}
+ if (isBooleanSpeculation(forNode(node->child1()).m_type)) {
+ forNode(node).setType(SpecBoolInt32);
+ break;
+ }
+
forNode(node).setType(SpecInt32);
break;
}
setConstant(node, jsDoubleNumber(child.asNumber()));
break;
}
- forNode(node).setType(forNode(node->child1()).m_type);
- forNode(node).fixTypeForRepresentation(node);
+
+ SpeculatedType type = forNode(node->child1()).m_type;
+ switch (node->child1().useKind()) {
+ case NotCellUse: {
+ if (type & SpecOther) {
+ type &= ~SpecOther;
+ type |= SpecDoublePureNaN | SpecBoolInt32; // Null becomes zero, undefined becomes NaN.
+ }
+ if (type & SpecBoolean) {
+ type &= ~SpecBoolean;
+ type |= SpecBoolInt32; // True becomes 1, false becomes 0.
+ }
+ type &= SpecBytecodeNumber;
+ break;
+ }
+
+ case Int52RepUse:
+ case NumberUse:
+ case RealNumberUse:
+ break;
+
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+ forNode(node).setType(type);
+ forNode(node).fixTypeForRepresentation(m_graph, node);
break;
}
break;
}
- forNode(node).setType(forNode(node->child1()).m_type & ~SpecDoubleImpureNaN);
- forNode(node).fixTypeForRepresentation(node);
+ forNode(node).setType(m_graph, forNode(node->child1()).m_type & ~SpecDoubleImpureNaN);
+ forNode(node).fixTypeForRepresentation(m_graph, node);
break;
}
case ValueAdd: {
ASSERT(node->binaryUseKind() == UntypedUse);
clobberWorld(node->origin.semantic, clobberLimit);
- forNode(node).setType(SpecString | SpecBytecodeNumber);
+ forNode(node).setType(m_graph, SpecString | SpecBytecodeNumber);
break;
}
}
}
forNode(node).setType(SpecInt32);
- if (shouldCheckOverflow(node->arithMode()))
- node->setCanExit(true);
break;
case Int52RepUse:
if (left && right && left.isMachineInt() && right.isMachineInt()) {
}
}
forNode(node).setType(SpecMachineInt);
- if (!forNode(node->child1()).isType(SpecInt32)
- || !forNode(node->child2()).isType(SpecInt32))
- node->setCanExit(true);
break;
case DoubleRepUse:
if (left && right && left.isNumber() && right.isNumber()) {
}
break;
}
-
+
+ case ArithClz32: {
+ JSValue operand = forNode(node->child1()).value();
+ if (operand && operand.isNumber()) {
+ uint32_t value = toUInt32(operand.asNumber());
+ setConstant(node, jsNumber(clz32(value)));
+ break;
+ }
+ forNode(node).setType(SpecInt32);
+ break;
+ }
+
case MakeRope: {
- node->setCanExit(true);
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
}
}
}
forNode(node).setType(SpecInt32);
- if (shouldCheckOverflow(node->arithMode()))
- node->setCanExit(true);
break;
case Int52RepUse:
if (left && right && left.isMachineInt() && right.isMachineInt()) {
}
}
forNode(node).setType(SpecMachineInt);
- if (!forNode(node->child1()).isType(SpecInt32)
- || !forNode(node->child2()).isType(SpecInt32))
- node->setCanExit(true);
break;
case DoubleRepUse:
if (left && right && left.isNumber() && right.isNumber()) {
}
}
forNode(node).setType(SpecInt32);
- if (shouldCheckOverflow(node->arithMode()))
- node->setCanExit(true);
break;
case Int52RepUse:
if (child && child.isMachineInt()) {
}
}
forNode(node).setType(SpecMachineInt);
- if (m_state.forNode(node->child1()).couldBeType(SpecInt52))
- node->setCanExit(true);
- if (shouldCheckNegativeZero(node->arithMode()))
- node->setCanExit(true);
break;
case DoubleRepUse:
if (child && child.isNumber()) {
}
}
forNode(node).setType(SpecInt32);
- if (shouldCheckOverflow(node->arithMode()))
- node->setCanExit(true);
break;
case Int52RepUse:
if (left && right && left.isMachineInt() && right.isMachineInt()) {
}
}
forNode(node).setType(SpecMachineInt);
- node->setCanExit(true);
break;
case DoubleRepUse:
if (left && right && left.isNumber() && right.isNumber()) {
}
}
forNode(node).setType(SpecInt32);
- node->setCanExit(true);
break;
case DoubleRepUse:
if (left && right && left.isNumber() && right.isNumber()) {
}
}
forNode(node).setType(SpecInt32);
- node->setCanExit(true);
break;
case DoubleRepUse:
if (left && right && left.isNumber() && right.isNumber()) {
break;
}
forNode(node).setType(SpecInt32);
- node->setCanExit(true);
break;
case DoubleRepUse:
if (left && right && left.isNumber() && right.isNumber()) {
break;
}
forNode(node).setType(SpecInt32);
- node->setCanExit(true);
break;
case DoubleRepUse:
if (left && right && left.isNumber() && right.isNumber()) {
}
}
forNode(node).setType(SpecInt32);
- node->setCanExit(true);
break;
case DoubleRepUse:
if (child && child.isNumber()) {
- setConstant(node, jsDoubleNumber(child.asNumber()));
+ setConstant(node, jsDoubleNumber(fabs(child.asNumber())));
break;
}
forNode(node).setType(typeOfDoubleAbs(forNode(node->child1()).m_type));
}
break;
}
+
+ case ArithPow: {
+ JSValue childY = forNode(node->child2()).value();
+ if (childY && childY.isNumber()) {
+ if (!childY.asNumber()) {
+ setConstant(node, jsDoubleNumber(1));
+ break;
+ }
+
+ JSValue childX = forNode(node->child1()).value();
+ if (childX && childX.isNumber()) {
+ setConstant(node, jsDoubleNumber(operationMathPow(childX.asNumber(), childY.asNumber())));
+ break;
+ }
+ }
+ forNode(node).setType(typeOfDoublePow(forNode(node->child1()).m_type, forNode(node->child2()).m_type));
+ break;
+ }
+
+ case ArithRound: {
+ JSValue operand = forNode(node->child1()).value();
+ if (operand && operand.isNumber()) {
+ double roundedValue = jsRound(operand.asNumber());
+
+ if (producesInteger(node->arithRoundingMode())) {
+ int32_t roundedValueAsInt32 = static_cast<int32_t>(roundedValue);
+ if (roundedValueAsInt32 == roundedValue) {
+ if (shouldCheckNegativeZero(node->arithRoundingMode())) {
+ if (roundedValueAsInt32 || !std::signbit(roundedValue)) {
+ setConstant(node, jsNumber(roundedValueAsInt32));
+ break;
+ }
+ } else {
+ setConstant(node, jsNumber(roundedValueAsInt32));
+ break;
+ }
+ }
+ } else {
+ setConstant(node, jsDoubleNumber(roundedValue));
+ break;
+ }
+ }
+ if (producesInteger(node->arithRoundingMode()))
+ forNode(node).setType(SpecInt32);
+ else
+ forNode(node).setType(typeOfDoubleRounding(forNode(node->child1()).m_type));
+ break;
+ }
case ArithSqrt: {
JSValue child = forNode(node->child1()).value();
setConstant(node, jsDoubleNumber(static_cast<float>(child.asNumber())));
break;
}
- forNode(node).setType(typeOfDoubleFRound(forNode(node->child1()).m_type));
+ forNode(node).setType(typeOfDoubleRounding(forNode(node->child1()).m_type));
break;
}
forNode(node).setType(typeOfDoubleUnaryOp(forNode(node->child1()).m_type));
break;
}
+
+ case ArithLog: {
+ JSValue child = forNode(node->child1()).value();
+ if (child && child.isNumber()) {
+ setConstant(node, jsDoubleNumber(log(child.asNumber())));
+ break;
+ }
+ forNode(node).setType(typeOfDoubleUnaryOp(forNode(node->child1()).m_type));
+ break;
+ }
case LogicalNot: {
switch (booleanResult(node, forNode(node->child1()))) {
setConstant(node, jsBoolean(true));
break;
default:
- switch (node->child1().useKind()) {
- case BooleanUse:
- case Int32Use:
- case DoubleRepUse:
- case UntypedUse:
- case StringUse:
- break;
- case ObjectOrOtherUse:
- node->setCanExit(true);
- break;
- default:
- RELEASE_ASSERT_NOT_REACHED();
- break;
- }
forNode(node).setType(SpecBoolean);
break;
}
case IsNumber:
case IsString:
case IsObject:
+ case IsObjectOrNull:
case IsFunction: {
- node->setCanExit(
- node->op() == IsUndefined
- && m_graph.masqueradesAsUndefinedWatchpointIsStillValid(node->origin.semantic));
- JSValue child = forNode(node->child1()).value();
- if (child) {
+ AbstractValue child = forNode(node->child1());
+ if (child.value()) {
bool constantWasSet = true;
switch (node->op()) {
case IsUndefined:
setConstant(node, jsBoolean(
- child.isCell()
- ? child.asCell()->structure()->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node->origin.semantic))
- : child.isUndefined()));
+ child.value().isCell()
+ ? child.value().asCell()->structure()->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node->origin.semantic))
+ : child.value().isUndefined()));
break;
case IsBoolean:
- setConstant(node, jsBoolean(child.isBoolean()));
+ setConstant(node, jsBoolean(child.value().isBoolean()));
break;
case IsNumber:
- setConstant(node, jsBoolean(child.isNumber()));
+ setConstant(node, jsBoolean(child.value().isNumber()));
break;
case IsString:
- setConstant(node, jsBoolean(isJSString(child)));
+ setConstant(node, jsBoolean(isJSString(child.value())));
break;
case IsObject:
- if (child.isNull() || !child.isObject()) {
- setConstant(node, jsBoolean(child.isNull()));
- break;
- }
- constantWasSet = false;
+ setConstant(node, jsBoolean(child.value().isObject()));
+ break;
+ case IsObjectOrNull:
+ if (child.value().isObject()) {
+ JSObject* object = asObject(child.value());
+ if (object->type() == JSFunctionType)
+ setConstant(node, jsBoolean(false));
+ else if (!(object->inlineTypeFlags() & TypeOfShouldCallGetCallData))
+ setConstant(node, jsBoolean(!child.value().asCell()->structure()->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node->origin.semantic))));
+ else {
+ // FIXME: This could just call getCallData.
+ // https://bugs.webkit.org/show_bug.cgi?id=144457
+ constantWasSet = false;
+ }
+ } else
+ setConstant(node, jsBoolean(child.value().isNull()));
+ break;
+ case IsFunction:
+ if (child.value().isObject()) {
+ JSObject* object = asObject(child.value());
+ if (object->type() == JSFunctionType)
+ setConstant(node, jsBoolean(true));
+ else if (!(object->inlineTypeFlags() & TypeOfShouldCallGetCallData))
+ setConstant(node, jsBoolean(false));
+ else {
+ // FIXME: This could just call getCallData.
+ // https://bugs.webkit.org/show_bug.cgi?id=144457
+ constantWasSet = false;
+ }
+ } else
+ setConstant(node, jsBoolean(false));
break;
default:
constantWasSet = false;
if (constantWasSet)
break;
}
-
+
+ // FIXME: This code should really use AbstractValue::isType() and
+ // AbstractValue::couldBeType().
+ // https://bugs.webkit.org/show_bug.cgi?id=146870
+
+ bool constantWasSet = false;
+ switch (node->op()) {
+ case IsUndefined:
+ // FIXME: Use the masquerades-as-undefined watchpoint thingy.
+ // https://bugs.webkit.org/show_bug.cgi?id=144456
+
+ if (!(child.m_type & (SpecOther | SpecObjectOther))) {
+ setConstant(node, jsBoolean(false));
+ constantWasSet = true;
+ break;
+ }
+
+ break;
+ case IsBoolean:
+ if (!(child.m_type & ~SpecBoolean)) {
+ setConstant(node, jsBoolean(true));
+ constantWasSet = true;
+ break;
+ }
+
+ if (!(child.m_type & SpecBoolean)) {
+ setConstant(node, jsBoolean(false));
+ constantWasSet = true;
+ break;
+ }
+
+ break;
+ case IsNumber:
+ if (!(child.m_type & ~SpecFullNumber)) {
+ setConstant(node, jsBoolean(true));
+ constantWasSet = true;
+ break;
+ }
+
+ if (!(child.m_type & SpecFullNumber)) {
+ setConstant(node, jsBoolean(false));
+ constantWasSet = true;
+ break;
+ }
+
+ break;
+ case IsString:
+ if (!(child.m_type & ~SpecString)) {
+ setConstant(node, jsBoolean(true));
+ constantWasSet = true;
+ break;
+ }
+
+ if (!(child.m_type & SpecString)) {
+ setConstant(node, jsBoolean(false));
+ constantWasSet = true;
+ break;
+ }
+
+ break;
+ case IsObject:
+ if (!(child.m_type & ~SpecObject)) {
+ setConstant(node, jsBoolean(true));
+ constantWasSet = true;
+ break;
+ }
+
+ if (!(child.m_type & SpecObject)) {
+ setConstant(node, jsBoolean(false));
+ constantWasSet = true;
+ break;
+ }
+
+ break;
+ case IsObjectOrNull:
+ // FIXME: Use the masquerades-as-undefined watchpoint thingy.
+ // https://bugs.webkit.org/show_bug.cgi?id=144456
+
+ // These expressions are complicated to parse. A helpful way to parse this is that
+ // "!(T & ~S)" means "T is a subset of S". Conversely, "!(T & S)" means "T is a
+ // disjoint set from S". Things like "T - S" means that, provided that S is a
+ // subset of T, it's the "set of all things in T but not in S". Things like "T | S"
+ // mean the "union of T and S".
+
+ // Is the child's type an object that isn't an other-object (i.e. object that could
+ // have masquaredes-as-undefined traps) and isn't a function? Then: we should fold
+ // this to true.
+ if (!(child.m_type & ~(SpecObject - SpecObjectOther - SpecFunction))) {
+ setConstant(node, jsBoolean(true));
+ constantWasSet = true;
+ break;
+ }
+
+ // Is the child's type definitely not either of: an object that isn't a function,
+ // or either undefined or null? Then: we should fold this to false. This means
+ // for example that if it's any non-function object, including those that have
+ // masquerades-as-undefined traps, then we don't fold. It also means we won't fold
+ // if it's undefined-or-null, since the type bits don't distinguish between
+ // undefined (which should fold to false) and null (which should fold to true).
+ if (!(child.m_type & ((SpecObject - SpecFunction) | SpecOther))) {
+ setConstant(node, jsBoolean(false));
+ constantWasSet = true;
+ break;
+ }
+
+ break;
+ case IsFunction:
+ if (!(child.m_type & ~SpecFunction)) {
+ setConstant(node, jsBoolean(true));
+ constantWasSet = true;
+ break;
+ }
+
+ if (!(child.m_type & (SpecFunction | SpecObjectOther))) {
+ setConstant(node, jsBoolean(false));
+ constantWasSet = true;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ if (constantWasSet)
+ break;
+
forNode(node).setType(SpecBoolean);
break;
}
AbstractValue& abstractChild = forNode(node->child1());
if (child) {
JSValue typeString = jsTypeStringForValue(*vm, m_codeBlock->globalObjectFor(node->origin.semantic), child);
- setConstant(node, typeString);
+ setConstant(node, *m_graph.freeze(typeString));
break;
}
if (isFullNumberSpeculation(abstractChild.m_type)) {
- setConstant(node, vm->smallStrings.numberString());
+ setConstant(node, *m_graph.freeze(vm->smallStrings.numberString()));
break;
}
if (isStringSpeculation(abstractChild.m_type)) {
- setConstant(node, vm->smallStrings.stringString());
+ setConstant(node, *m_graph.freeze(vm->smallStrings.stringString()));
break;
}
-
- if (isFinalObjectSpeculation(abstractChild.m_type) || isArraySpeculation(abstractChild.m_type) || isArgumentsSpeculation(abstractChild.m_type)) {
- setConstant(node, vm->smallStrings.objectString());
+
+ // FIXME: We could use the masquerades-as-undefined watchpoint here.
+ // https://bugs.webkit.org/show_bug.cgi?id=144456
+ if (!(abstractChild.m_type & ~(SpecObject - SpecObjectOther))) {
+ setConstant(node, *m_graph.freeze(vm->smallStrings.objectString()));
break;
}
if (isFunctionSpeculation(abstractChild.m_type)) {
- setConstant(node, vm->smallStrings.functionString());
+ setConstant(node, *m_graph.freeze(vm->smallStrings.functionString()));
break;
}
if (isBooleanSpeculation(abstractChild.m_type)) {
- setConstant(node, vm->smallStrings.booleanString());
+ setConstant(node, *m_graph.freeze(vm->smallStrings.booleanString()));
break;
}
- switch (node->child1().useKind()) {
- case StringUse:
- case CellUse:
- node->setCanExit(true);
- break;
- case UntypedUse:
- break;
- default:
- RELEASE_ASSERT_NOT_REACHED();
+ if (isSymbolSpeculation(abstractChild.m_type)) {
+ setConstant(node, *m_graph.freeze(vm->smallStrings.symbolString()));
break;
}
- forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
+
+ forNode(node).setType(m_graph, SpecStringIdent);
break;
}
}
forNode(node).setType(SpecBoolean);
-
- // This is overly conservative. But the only thing this prevents is store elimination,
- // and how likely is it, really, that you'll have redundant stores across a comparison
- // operation? Comparison operations are typically at the end of basic blocks, so
- // unless we have global store elimination (super unlikely given how unprofitable that
- // optimization is to begin with), you aren't going to be wanting to store eliminate
- // across an equality op.
- node->setCanExit(true);
break;
}
}
forNode(node).setType(SpecBoolean);
- node->setCanExit(true); // This is overly conservative.
break;
}
case StringCharCodeAt:
- node->setCanExit(true);
forNode(node).setType(SpecInt32);
break;
case StringFromCharCode:
- forNode(node).setType(SpecString);
+ forNode(node).setType(m_graph, SpecString);
break;
case StringCharAt:
- node->setCanExit(true);
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
case GetByVal: {
- node->setCanExit(true);
switch (node->arrayMode().type()) {
case Array::SelectUsingPredictions:
case Array::Unprofiled:
} else
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
- case Array::Arguments:
+ case Array::DirectArguments:
+ case Array::ScopedArguments:
forNode(node).makeHeapTop();
break;
case Array::Int32:
case PutByValDirect:
case PutByVal:
case PutByValAlias: {
- node->setCanExit(true);
switch (node->arrayMode().modeForPut().type()) {
case Array::ForceExit:
m_state.setIsValid(false);
}
case ArrayPush:
- node->setCanExit(true);
clobberWorld(node->origin.semantic, clobberLimit);
forNode(node).setType(SpecBytecodeNumber);
break;
case ArrayPop:
- node->setCanExit(true);
clobberWorld(node->origin.semantic, clobberLimit);
forNode(node).makeHeapTop();
break;
+
+ case GetMyArgumentByVal: {
+ JSValue index = forNode(node->child2()).m_value;
+ InlineCallFrame* inlineCallFrame = node->child1()->origin.semantic.inlineCallFrame;
+
+ if (index && index.isInt32()) {
+ // This pretends to return TOP for accesses that are actually proven out-of-bounds because
+ // that's the conservative thing to do. Otherwise we'd need to write more code to mark such
+ // paths as unreachable, and it's almost certainly not worth the effort.
+
+ if (inlineCallFrame) {
+ if (index.asUInt32() < inlineCallFrame->arguments.size() - 1) {
+ forNode(node) = m_state.variables().operand(
+ virtualRegisterForArgument(index.asInt32() + 1) + inlineCallFrame->stackOffset);
+ m_state.setFoundConstants(true);
+ break;
+ }
+ } else {
+ if (index.asUInt32() < m_state.variables().numberOfArguments() - 1) {
+ forNode(node) = m_state.variables().argument(index.asInt32() + 1);
+ m_state.setFoundConstants(true);
+ break;
+ }
+ }
+ }
+
+ if (inlineCallFrame) {
+ // We have a bound on the types even though it's random access. Take advantage of this.
+
+ AbstractValue result;
+ for (unsigned i = inlineCallFrame->arguments.size(); i-- > 1;) {
+ result.merge(
+ m_state.variables().operand(
+ virtualRegisterForArgument(i) + inlineCallFrame->stackOffset));
+ }
+
+ if (result.value())
+ m_state.setFoundConstants(true);
+ forNode(node) = result;
+ break;
+ }
+
+ forNode(node).makeHeapTop();
+ break;
+ }
case RegExpExec:
forNode(node).makeHeapTop();
// constant propagation, but we can do better:
// We can specialize the source variable's value on each direction of
// the branch.
- node->setCanExit(true); // This is overly conservative.
m_state.setBranchDirection(TakeBoth);
break;
}
case Throw:
case ThrowReferenceError:
m_state.setIsValid(false);
- node->setCanExit(true);
break;
case ToPrimitive: {
break;
}
- if (!(forNode(node->child1()).m_type & ~(SpecFullNumber | SpecBoolean | SpecString))) {
+ if (!(forNode(node->child1()).m_type & ~(SpecFullNumber | SpecBoolean | SpecString | SpecSymbol))) {
m_state.setFoundConstants(true);
forNode(node) = forNode(node->child1());
break;
clobberWorld(node->origin.semantic, clobberLimit);
- forNode(node).setType((SpecHeapTop & ~SpecCell) | SpecString);
+ forNode(node).setType(m_graph, (SpecHeapTop & ~SpecCell) | SpecString | SpecSymbol);
break;
}
- case ToString: {
+ case ToString:
+ case CallStringConstructor: {
switch (node->child1().useKind()) {
case StringObjectUse:
// This also filters that the StringObject has the primordial StringObject
filter(
node->child1(),
m_graph.globalObjectFor(node->origin.semantic)->stringObjectStructure());
- node->setCanExit(true); // We could be more precise but it's likely not worth it.
break;
case StringOrStringObjectUse:
- node->setCanExit(true); // We could be more precise but it's likely not worth it.
break;
case CellUse:
case UntypedUse:
}
case NewArray:
- node->setCanExit(true);
forNode(node).set(
m_graph,
m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType()));
- m_state.setHaveStructures(true);
break;
case NewArrayBuffer:
- node->setCanExit(true);
forNode(node).set(
m_graph,
m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType()));
- m_state.setHaveStructures(true);
break;
case NewArrayWithSize:
- node->setCanExit(true);
- forNode(node).setType(SpecArray);
- m_state.setHaveStructures(true);
+ forNode(node).setType(m_graph, SpecArray);
break;
case NewTypedArray:
m_graph,
m_graph.globalObjectFor(node->origin.semantic)->typedArrayStructure(
node->typedArrayType()));
- m_state.setHaveStructures(true);
break;
case NewRegexp:
forNode(node).set(m_graph, m_graph.globalObjectFor(node->origin.semantic)->regExpStructure());
- m_state.setHaveStructures(true);
break;
case ToThis: {
}
case CreateThis: {
- forNode(node).setType(SpecFinalObject);
+ // FIXME: We can fold this to NewObject if the incoming callee is a constant.
+ forNode(node).setType(m_graph, SpecFinalObject);
break;
}
- case AllocationProfileWatchpoint:
- node->setCanExit(true);
- break;
-
case NewObject:
ASSERT(node->structure());
forNode(node).set(m_graph, node->structure());
- m_state.setHaveStructures(true);
break;
+ case PhantomNewObject:
+ case PhantomNewFunction:
+ case PhantomCreateActivation:
+ case PhantomDirectArguments:
+ case PhantomClonedArguments:
+ case BottomValue:
+ m_state.setDidClobber(true); // Prevent constant folding.
+ // This claims to return bottom.
+ break;
+
+ case PutHint:
+ break;
+
+ case MaterializeNewObject: {
+ StructureSet set;
+
+ m_phiChildren->forAllTransitiveIncomingValues(
+ m_graph.varArgChild(node, 0).node(),
+ [&] (Node* incoming) {
+ set.add(incoming->castConstant<Structure*>());
+ });
+
+ forNode(node).set(m_graph, set);
+ break;
+ }
+
case CreateActivation:
+ case MaterializeCreateActivation:
forNode(node).set(
m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->activationStructure());
- m_state.setHaveStructures(true);
break;
- case FunctionReentryWatchpoint:
- case TypedArrayWatchpoint:
- break;
-
- case CreateArguments:
- forNode(node) = forNode(node->child1());
- forNode(node).filter(~SpecEmpty);
- forNode(node).merge(SpecArguments);
+ case CreateDirectArguments:
+ forNode(node).set(m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->directArgumentsStructure());
break;
- case TearOffActivation:
- case TearOffArguments:
- // Does nothing that is user-visible.
- break;
-
- case CheckArgumentsNotCreated:
- if (isEmptySpeculation(
- m_state.variables().operand(
- m_graph.argumentsRegisterFor(node->origin.semantic).offset()).m_type))
- m_state.setFoundConstants(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->origin.semantic.inlineCallFrame) {
- forNode(node).set(
- m_graph, jsNumber(node->origin.semantic.inlineCallFrame->arguments.size() - 1));
- } else
- forNode(node).setType(SpecInt32);
- node->setCanExit(
- !isEmptySpeculation(
- m_state.variables().operand(
- m_graph.argumentsRegisterFor(node->origin.semantic)).m_type));
+ case CreateScopedArguments:
+ forNode(node).set(m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->scopedArgumentsStructure());
break;
- case GetMyArgumentsLengthSafe:
- // This potentially clobbers all structures if the arguments object had a getter
- // installed on the length property.
- clobberWorld(node->origin.semantic, clobberLimit);
- // We currently make no guarantee about what this returns because it does not
- // speculate that the length property is actually a length.
- forNode(node).makeHeapTop();
+ case CreateClonedArguments:
+ forNode(node).setType(m_graph, SpecObjectOther);
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).makeHeapTop();
+ case NewFunction:
+ forNode(node).set(
+ m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->functionStructure());
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->origin.semantic, clobberLimit);
- // And the result is unknown.
- forNode(node).makeHeapTop();
+ case GetCallee:
+ if (FunctionExecutable* executable = jsDynamicCast<FunctionExecutable*>(m_codeBlock->ownerExecutable())) {
+ InferredValue* singleton = executable->singletonFunction();
+ if (JSValue value = singleton->inferredValue()) {
+ m_graph.watchpoints().addLazily(singleton);
+ JSFunction* function = jsCast<JSFunction*>(value);
+ setConstant(node, *m_graph.freeze(function));
+ break;
+ }
+ }
+ forNode(node).setType(m_graph, SpecFunction);
break;
- case NewFunction: {
- AbstractValue& value = forNode(node);
- value = forNode(node->child1());
+ case GetArgumentCount:
+ forNode(node).setType(SpecInt32);
+ break;
- if (!(value.m_type & SpecEmpty)) {
- m_state.setFoundConstants(true);
- break;
+ case GetGetter: {
+ JSValue base = forNode(node->child1()).m_value;
+ if (base) {
+ GetterSetter* getterSetter = jsCast<GetterSetter*>(base);
+ if (!getterSetter->isGetterNull()) {
+ setConstant(node, *m_graph.freeze(getterSetter->getterConcurrently()));
+ break;
+ }
}
-
- value.setType((value.m_type & ~SpecEmpty) | SpecFunction);
+
+ forNode(node).setType(m_graph, SpecObject);
break;
}
-
- case NewFunctionExpression:
- case NewFunctionNoCheck:
- forNode(node).set(
- m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->functionStructure());
- m_state.setHaveStructures(true);
- break;
- case GetCallee:
- forNode(node).setType(SpecFunction);
+ case GetSetter: {
+ JSValue base = forNode(node->child1()).m_value;
+ if (base) {
+ GetterSetter* getterSetter = jsCast<GetterSetter*>(base);
+ if (!getterSetter->isSetterNull()) {
+ setConstant(node, *m_graph.freeze(getterSetter->setterConcurrently()));
+ break;
+ }
+ }
+
+ forNode(node).setType(m_graph, SpecObject);
break;
+ }
- case GetScope: // FIXME: We could get rid of these if we know that the JSFunction is a constant. https://bugs.webkit.org/show_bug.cgi?id=106202
- case GetMyScope:
- case SkipTopScope:
- forNode(node).setType(SpecObjectOther);
+ case GetScope:
+ if (JSValue base = forNode(node->child1()).m_value) {
+ if (JSFunction* function = jsDynamicCast<JSFunction*>(base)) {
+ setConstant(node, *m_graph.freeze(function->scope()));
+ break;
+ }
+ }
+ forNode(node).setType(m_graph, SpecObjectOther);
break;
case SkipScope: {
JSValue child = forNode(node->child1()).value();
if (child) {
- setConstant(node, JSValue(jsCast<JSScope*>(child.asCell())->next()));
+ setConstant(node, *m_graph.freeze(JSValue(jsCast<JSScope*>(child.asCell())->next())));
break;
}
- forNode(node).setType(SpecObjectOther);
+ forNode(node).setType(m_graph, SpecObjectOther);
break;
}
- case GetClosureRegisters:
- forNode(node).clear(); // The result is not a JS value.
- break;
-
case GetClosureVar:
+ if (JSValue value = m_graph.tryGetConstantClosureVar(forNode(node->child1()), node->scopeOffset())) {
+ setConstant(node, *m_graph.freeze(value));
+ break;
+ }
forNode(node).makeHeapTop();
break;
case PutClosureVar:
- clobberCapturedVars(node->origin.semantic);
+ break;
+
+ case GetFromArguments:
+ forNode(node).makeHeapTop();
+ break;
+
+ case PutToArguments:
break;
case GetById:
- case GetByIdFlush:
- node->setCanExit(true);
+ case GetByIdFlush: {
if (!node->prediction()) {
m_state.setIsValid(false);
break;
}
- if (isCellSpeculation(node->child1()->prediction())) {
- if (Structure* structure = forNode(node->child1()).bestProvenStructure()) {
- GetByIdStatus status = GetByIdStatus::computeFor(
- m_graph.m_vm, structure,
- m_graph.identifiers()[node->identifierNumber()]);
- if (status.isSimple() && status.numVariants() == 1) {
- // Assert things that we can't handle and that the computeFor() method
- // above won't be able to return.
- ASSERT(status[0].structureSet().size() == 1);
- ASSERT(!status[0].chain());
-
- if (status[0].specificValue())
- setConstant(node, status[0].specificValue());
- else
- forNode(node).makeHeapTop();
- filter(node->child1(), status[0].structureSet());
+
+ AbstractValue& value = forNode(node->child1());
+ if (!value.m_structure.isTop() && !value.m_structure.isClobbered()
+ && (node->child1().useKind() == CellUse || !(value.m_type & ~SpecCell))) {
+ GetByIdStatus status = GetByIdStatus::computeFor(
+ value.m_structure.set(), m_graph.identifiers()[node->identifierNumber()]);
+ if (status.isSimple()) {
+ // Figure out what the result is going to be - is it TOP, a constant, or maybe
+ // something more subtle?
+ AbstractValue result;
+ for (unsigned i = status.numVariants(); i--;) {
+ DFG_ASSERT(m_graph, node, !status[i].alternateBase());
+ JSValue constantResult =
+ m_graph.tryGetConstantProperty(value, status[i].offset());
+ if (!constantResult) {
+ result.makeHeapTop();
+ break;
+ }
- m_state.setFoundConstants(true);
- m_state.setHaveStructures(true);
- break;
+ AbstractValue thisResult;
+ thisResult.set(
+ m_graph, *m_graph.freeze(constantResult),
+ m_state.structureClobberState());
+ result.merge(thisResult);
}
+ if (status.numVariants() == 1 || isFTL(m_graph.m_plan.mode))
+ m_state.setFoundConstants(true);
+ forNode(node) = result;
+ break;
}
}
+
clobberWorld(node->origin.semantic, clobberLimit);
forNode(node).makeHeapTop();
break;
+ }
- case GetArrayLength:
- node->setCanExit(true); // Lies, but it's true for the common case of JSArray, so it's good enough.
+ case GetArrayLength: {
+ JSArrayBufferView* view = m_graph.tryGetFoldableView(
+ forNode(node->child1()).m_value, node->arrayMode());
+ if (view) {
+ setConstant(node, jsNumber(view->length()));
+ break;
+ }
forNode(node).setType(SpecInt32);
break;
-
- case CheckExecutable: {
- // FIXME: We could track executables in AbstractValue, which would allow us to get rid of these checks
- // more thoroughly. https://bugs.webkit.org/show_bug.cgi?id=106200
- // FIXME: We could eliminate these entirely if we know the exact value that flows into this.
- // https://bugs.webkit.org/show_bug.cgi?id=106201
- node->setCanExit(true);
- break;
}
-
+
case CheckStructure: {
- // FIXME: We should be able to propagate the structure sets of constants (i.e. prototypes).
AbstractValue& value = forNode(node->child1());
ASSERT(!(value.m_type & ~SpecCell)); // Edge filtering should have already ensured this.
StructureSet& set = node->structureSet();
-
- if (value.m_currentKnownStructure.isSubsetOf(set)) {
- m_state.setFoundConstants(true);
- break;
- }
-
- node->setCanExit(true);
- m_state.setHaveStructures(true);
-
- // 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.
- if (value.m_futurePossibleStructure.isSubsetOf(set)
- && value.m_futurePossibleStructure.hasSingleton()) {
+
+ // It's interesting that we could have proven that the object has a larger structure set
+ // that includes the set we're testing. In that case we could make the structure check
+ // more efficient. We currently don't.
+
+ if (value.m_structure.isSubsetOf(set)) {
m_state.setFoundConstants(true);
- filter(value, value.m_futurePossibleStructure.singleton());
break;
}
break;
}
- case StructureTransitionWatchpoint: {
+ case CheckStructureImmediate: {
+ // FIXME: This currently can only reason about one structure at a time.
+ // https://bugs.webkit.org/show_bug.cgi?id=136988
+
AbstractValue& value = forNode(node->child1());
-
- filter(value, node->structure());
- m_state.setHaveStructures(true);
- node->setCanExit(true);
+ StructureSet& set = node->structureSet();
+
+ if (value.value()) {
+ if (Structure* structure = jsDynamicCast<Structure*>(value.value())) {
+ if (set.contains(structure)) {
+ m_state.setFoundConstants(true);
+ break;
+ }
+ }
+ m_state.setIsValid(false);
+ break;
+ }
+
+ if (m_phiChildren) {
+ bool allGood = true;
+ m_phiChildren->forAllTransitiveIncomingValues(
+ node,
+ [&] (Node* incoming) {
+ if (Structure* structure = incoming->dynamicCastConstant<Structure*>()) {
+ if (set.contains(structure))
+ return;
+ }
+ allGood = false;
+ });
+ if (allGood) {
+ m_state.setFoundConstants(true);
+ break;
+ }
+ }
+
+ if (Structure* structure = set.onlyStructure()) {
+ filterByValue(node->child1(), *m_graph.freeze(structure));
+ break;
+ }
+
+ // Aw shucks, we can't do anything!
break;
}
-
+
case PutStructure:
- case PhantomPutStructure:
- if (!forNode(node->child1()).m_currentKnownStructure.isClear()) {
- clobberStructures(clobberLimit);
- forNode(node->child1()).set(m_graph, node->structureTransitionData().newStructure);
- m_state.setHaveStructures(true);
+ if (!forNode(node->child1()).m_structure.isClear()) {
+ if (forNode(node->child1()).m_structure.onlyStructure() == node->transition()->next)
+ m_state.setFoundConstants(true);
+ else {
+ observeTransition(
+ clobberLimit, node->transition()->previous, node->transition()->next);
+ forNode(node->child1()).changeStructure(m_graph, node->transition()->next);
+ }
}
break;
case GetButterfly:
m_state.setFoundConstants(true);
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:
filter(node->child1(), SpecString);
case Array::ArrayStorage:
case Array::SlowPutArrayStorage:
break;
- case Array::Arguments:
- filter(node->child1(), SpecArguments);
+ case Array::DirectArguments:
+ filter(node->child1(), SpecDirectArguments);
+ break;
+ case Array::ScopedArguments:
+ filter(node->child1(), SpecScopedArguments);
break;
case Array::Int8Array:
filter(node->child1(), SpecInt8Array);
break;
}
filterArrayModes(node->child1(), node->arrayMode().arrayModesThatPassFiltering());
- m_state.setHaveStructures(true);
break;
}
case Arrayify: {
m_state.setFoundConstants(true);
break;
}
- ASSERT(node->arrayMode().conversion() == Array::Convert
- || node->arrayMode().conversion() == Array::RageConvert);
- node->setCanExit(true);
+ ASSERT(node->arrayMode().conversion() == Array::Convert);
clobberStructures(clobberLimit);
filterArrayModes(node->child1(), node->arrayMode().arrayModesThatPassFiltering());
- m_state.setHaveStructures(true);
break;
}
case ArrayifyToStructure: {
AbstractValue& value = forNode(node->child1());
- StructureSet set = node->structure();
- if (value.m_futurePossibleStructure.isSubsetOf(set)
- || value.m_currentKnownStructure.isSubsetOf(set))
+ if (value.m_structure.isSubsetOf(StructureSet(node->structure())))
m_state.setFoundConstants(true);
- node->setCanExit(true);
clobberStructures(clobberLimit);
- filter(value, set);
- m_state.setHaveStructures(true);
+
+ // We have a bunch of options of how to express the abstract set at this point. Let set S
+ // be the set of structures that the value had before clobbering and assume that all of
+ // them are watchable. The new value should be the least expressible upper bound of the
+ // intersection of "values that currently have structure = node->structure()" and "values
+ // that have structure in S plus any structure transition-reachable from S". Assume that
+ // node->structure() is not in S but it is transition-reachable from S. Then we would
+ // like to say that the result is "values that have structure = node->structure() until
+ // we invalidate", but there is no way to express this using the AbstractValue syntax. So
+ // we must choose between:
+ //
+ // 1) "values that currently have structure = node->structure()". This is a valid
+ // superset of the value that we really want, and it's specific enough to satisfy the
+ // preconditions of the array access that this is guarding. It's also specific enough
+ // to allow relevant optimizations in the case that we didn't have a contradiction
+ // like in this example. Notice that in the abscence of any contradiction, this result
+ // is precise rather than being a conservative LUB.
+ //
+ // 2) "values that currently hava structure in S plus any structure transition-reachable
+ // from S". This is also a valid superset of the value that we really want, but it's
+ // not specific enough to satisfy the preconditions of the array access that this is
+ // guarding - so playing such shenanigans would preclude us from having assertions on
+ // the typing preconditions of any array accesses. This would also not be a desirable
+ // answer in the absence of a contradiction.
+ //
+ // Note that it's tempting to simply say that the resulting value is BOTTOM because of
+ // the contradiction. That would be wrong, since we haven't hit an invalidation point,
+ // yet.
+ value.set(m_graph, node->structure());
+ break;
+ }
+ case GetIndexedPropertyStorage: {
+ JSArrayBufferView* view = m_graph.tryGetFoldableView(
+ forNode(node->child1()).m_value, node->arrayMode());
+ if (view)
+ m_state.setFoundConstants(true);
+ forNode(node).clear();
break;
}
- case GetIndexedPropertyStorage:
case ConstantStoragePointer: {
forNode(node).clear();
break;
}
case GetTypedArrayByteOffset: {
+ JSArrayBufferView* view = m_graph.tryGetFoldableView(forNode(node->child1()).m_value);
+ if (view) {
+ setConstant(node, jsNumber(view->byteOffset()));
+ break;
+ }
forNode(node).setType(SpecInt32);
break;
}
case GetByOffset: {
+ StorageAccessData& data = node->storageAccessData();
+ JSValue result = m_graph.tryGetConstantProperty(forNode(node->child2()), data.offset);
+ if (result) {
+ setConstant(node, *m_graph.freeze(result));
+ break;
+ }
+
forNode(node).makeHeapTop();
break;
}
+ case GetGetterSetterByOffset: {
+ StorageAccessData& data = node->storageAccessData();
+ JSValue result = m_graph.tryGetConstantProperty(forNode(node->child2()), data.offset);
+ if (result && jsDynamicCast<GetterSetter*>(result)) {
+ setConstant(node, *m_graph.freeze(result));
+ break;
+ }
+
+ forNode(node).set(m_graph, m_graph.m_vm.getterSetterStructure.get());
+ break;
+ }
+
case MultiGetByOffset: {
- AbstractValue& value = forNode(node->child1());
- ASSERT(!(value.m_type & ~SpecCell)); // Edge filtering should have already ensured this.
-
- if (Structure* structure = value.bestProvenStructure()) {
- bool done = false;
- for (unsigned i = node->multiGetByOffsetData().variants.size(); i--;) {
- const GetByIdVariant& variant = node->multiGetByOffsetData().variants[i];
- if (!variant.structureSet().contains(structure))
- continue;
-
- if (variant.chain())
- break;
-
- filter(value, structure);
- forNode(node).makeHeapTop();
- m_state.setFoundConstants(true);
- done = true;
- break;
+ // This code will filter the base value in a manner that is possibly different (either more
+ // or less precise) than the way it would be filtered if this was strength-reduced to a
+ // CheckStructure. This is fine. It's legal for different passes over the code to prove
+ // different things about the code, so long as all of them are sound. That even includes
+ // one guy proving that code should never execute (due to a contradiction) and another guy
+ // not finding that contradiction. If someone ever proved that there would be a
+ // contradiction then there must always be a contradiction even if subsequent passes don't
+ // realize it. This is the case here.
+
+ // Ordinarily you have to be careful with calling setFoundConstants()
+ // because of the effect on compile times, but this node is FTL-only.
+ m_state.setFoundConstants(true);
+
+ AbstractValue base = forNode(node->child1());
+ StructureSet baseSet;
+ AbstractValue result;
+ for (unsigned i = node->multiGetByOffsetData().variants.size(); i--;) {
+ GetByIdVariant& variant = node->multiGetByOffsetData().variants[i];
+ StructureSet set = variant.structureSet();
+ set.filter(base);
+ if (set.isEmpty())
+ continue;
+ baseSet.merge(set);
+
+ JSValue baseForLoad;
+ if (variant.alternateBase())
+ baseForLoad = variant.alternateBase();
+ else
+ baseForLoad = base.m_value;
+ JSValue constantResult =
+ m_graph.tryGetConstantProperty(
+ baseForLoad, variant.baseStructure(), variant.offset());
+ if (!constantResult) {
+ result.makeHeapTop();
+ continue;
}
- if (done)
- break;
+ AbstractValue thisResult;
+ thisResult.set(
+ m_graph,
+ *m_graph.freeze(constantResult),
+ m_state.structureClobberState());
+ result.merge(thisResult);
}
- StructureSet set;
- for (unsigned i = node->multiGetByOffsetData().variants.size(); i--;)
- set.addAll(node->multiGetByOffsetData().variants[i].structureSet());
+ if (forNode(node->child1()).changeStructure(m_graph, baseSet) == Contradiction)
+ m_state.setIsValid(false);
- filter(node->child1(), set);
- forNode(node).makeHeapTop();
+ forNode(node) = result;
break;
}
}
case MultiPutByOffset: {
- AbstractValue& value = forNode(node->child1());
- ASSERT(!(value.m_type & ~SpecCell)); // Edge filtering should have already ensured this.
-
- if (Structure* structure = value.bestProvenStructure()) {
- bool done = false;
- for (unsigned i = node->multiPutByOffsetData().variants.size(); i--;) {
- const PutByIdVariant& variant = node->multiPutByOffsetData().variants[i];
- if (variant.oldStructure() != structure)
- continue;
-
- if (variant.kind() == PutByIdVariant::Replace) {
- filter(node->child1(), structure);
- m_state.setFoundConstants(true);
- m_state.setHaveStructures(true);
- done = true;
- break;
- }
-
- ASSERT(variant.kind() == PutByIdVariant::Transition);
- clobberStructures(clobberLimit);
- forNode(node->child1()).set(m_graph, variant.newStructure());
- m_state.setFoundConstants(true);
- m_state.setHaveStructures(true);
- done = true;
- break;
- }
- if (done)
- break;
- }
+ StructureSet newSet;
+ TransitionVector transitions;
- clobberStructures(clobberLimit);
+ // Ordinarily you have to be careful with calling setFoundConstants()
+ // because of the effect on compile times, but this node is FTL-only.
+ m_state.setFoundConstants(true);
+
+ AbstractValue base = forNode(node->child1());
- StructureSet newSet;
for (unsigned i = node->multiPutByOffsetData().variants.size(); i--;) {
const PutByIdVariant& variant = node->multiPutByOffsetData().variants[i];
- if (variant.kind() == PutByIdVariant::Replace) {
- if (value.m_currentKnownStructure.contains(variant.structure()))
- newSet.addAll(variant.structure());
+ StructureSet thisSet = variant.oldStructure();
+ thisSet.filter(base);
+ if (thisSet.isEmpty())
continue;
+ if (variant.kind() == PutByIdVariant::Transition) {
+ if (thisSet.onlyStructure() != variant.newStructure()) {
+ transitions.append(
+ Transition(variant.oldStructureForTransition(), variant.newStructure()));
+ } // else this is really a replace.
+ newSet.add(variant.newStructure());
+ } else {
+ ASSERT(variant.kind() == PutByIdVariant::Replace);
+ newSet.merge(thisSet);
}
- ASSERT(variant.kind() == PutByIdVariant::Transition);
- if (value.m_currentKnownStructure.contains(variant.oldStructure()))
- newSet.addAll(variant.newStructure());
}
- // Use filter(value, set) as a way of setting the structure set. This works because
- // we would have already made the set be TOP before this. Filtering top is another
- // way of setting.
- filter(node->child1(), newSet);
+ observeTransitions(clobberLimit, transitions);
+ if (forNode(node->child1()).changeStructure(m_graph, newSet) == Contradiction)
+ m_state.setIsValid(false);
+ break;
+ }
+
+ case GetExecutable: {
+ JSValue value = forNode(node->child1()).value();
+ if (value) {
+ JSFunction* function = jsDynamicCast<JSFunction*>(value);
+ if (function) {
+ setConstant(node, *m_graph.freeze(function->executable()));
+ break;
+ }
+ }
+ forNode(node).setType(m_graph, SpecCellOther);
break;
}
- case CheckFunction: {
+ case CheckCell: {
JSValue value = forNode(node->child1()).value();
- if (value == node->function()) {
+ if (value == node->cellOperand()->value()) {
m_state.setFoundConstants(true);
ASSERT(value);
break;
}
-
- node->setCanExit(true); // Lies! We can do better.
- filterByValue(node->child1(), node->function());
+ filterByValue(node->child1(), *node->cellOperand());
break;
}
+
+ case CheckNotEmpty: {
+ AbstractValue& value = forNode(node->child1());
+ if (!(value.m_type & SpecEmpty)) {
+ m_state.setFoundConstants(true);
+ break;
+ }
+ filter(value, ~SpecEmpty);
+ break;
+ }
+
case CheckInBounds: {
JSValue left = forNode(node->child1()).value();
JSValue right = forNode(node->child2()).value();
m_state.setFoundConstants(true);
break;
}
-
- node->setCanExit(true);
break;
}
case PutById:
case PutByIdFlush:
- case PutByIdDirect:
- node->setCanExit(true);
- if (Structure* structure = forNode(node->child1()).bestProvenStructure()) {
+ case PutByIdDirect: {
+ AbstractValue& value = forNode(node->child1());
+ if (!value.m_structure.isTop() && !value.m_structure.isClobbered()) {
PutByIdStatus status = PutByIdStatus::computeFor(
- m_graph.m_vm,
m_graph.globalObjectFor(node->origin.semantic),
- structure,
+ value.m_structure.set(),
m_graph.identifiers()[node->identifierNumber()],
node->op() == PutByIdDirect);
- if (status.isSimple() && status.numVariants() == 1) {
- if (status[0].kind() == PutByIdVariant::Replace) {
- filter(node->child1(), structure);
- m_state.setFoundConstants(true);
- m_state.setHaveStructures(true);
- break;
+
+ if (status.isSimple()) {
+ StructureSet newSet;
+ TransitionVector transitions;
+
+ for (unsigned i = status.numVariants(); i--;) {
+ const PutByIdVariant& variant = status[i];
+ if (variant.kind() == PutByIdVariant::Transition) {
+ transitions.append(
+ Transition(
+ variant.oldStructureForTransition(), variant.newStructure()));
+ m_graph.registerStructure(variant.newStructure());
+ newSet.add(variant.newStructure());
+ } else {
+ ASSERT(variant.kind() == PutByIdVariant::Replace);
+ newSet.merge(variant.oldStructure());
+ }
}
- if (status[0].kind() == PutByIdVariant::Transition) {
- clobberStructures(clobberLimit);
- forNode(node->child1()).set(m_graph, status[0].newStructure());
- m_state.setHaveStructures(true);
+
+ if (status.numVariants() == 1 || isFTL(m_graph.m_plan.mode))
m_state.setFoundConstants(true);
- break;
- }
+
+ observeTransitions(clobberLimit, transitions);
+ if (forNode(node->child1()).changeStructure(m_graph, newSet) == Contradiction)
+ m_state.setIsValid(false);
+ break;
}
}
+
clobberWorld(node->origin.semantic, clobberLimit);
break;
+ }
- case In:
+ case In: {
// FIXME: We can determine when the property definitely exists based on abstract
// value information.
clobberWorld(node->origin.semantic, clobberLimit);
forNode(node).setType(SpecBoolean);
break;
+ }
+ case GetEnumerableLength: {
+ forNode(node).setType(SpecInt32);
+ break;
+ }
+ case HasGenericProperty: {
+ forNode(node).setType(SpecBoolean);
+ break;
+ }
+ case HasStructureProperty: {
+ forNode(node).setType(SpecBoolean);
+ break;
+ }
+ case HasIndexedProperty: {
+ ArrayMode mode = node->arrayMode();
+ switch (mode.type()) {
+ case Array::Int32:
+ case Array::Double:
+ case Array::Contiguous:
+ case Array::ArrayStorage: {
+ break;
+ }
+ default: {
+ clobberWorld(node->origin.semantic, clobberLimit);
+ break;
+ }
+ }
+ forNode(node).setType(SpecBoolean);
+ break;
+ }
+ case GetDirectPname: {
+ clobberWorld(node->origin.semantic, clobberLimit);
+ forNode(node).makeHeapTop();
+ break;
+ }
+ case GetPropertyEnumerator: {
+ forNode(node).setType(m_graph, SpecCell);
+ break;
+ }
+ case GetEnumeratorStructurePname: {
+ forNode(node).setType(m_graph, SpecString | SpecOther);
+ break;
+ }
+ case GetEnumeratorGenericPname: {
+ forNode(node).setType(m_graph, SpecString | SpecOther);
+ break;
+ }
+ case ToIndexString: {
+ forNode(node).setType(m_graph, SpecString);
+ break;
+ }
+
case GetGlobalVar:
forNode(node).makeHeapTop();
break;
- case VariableWatchpoint:
case VarInjectionWatchpoint:
- node->setCanExit(true);
- break;
-
case PutGlobalVar:
case NotifyWrite:
break;
case CheckHasInstance:
- node->setCanExit(true);
// 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
forNode(node).setType(SpecBoolean);
break;
case Phi:
RELEASE_ASSERT(m_graph.m_form == SSA);
- // The state of this node would have already been decided.
+ // The state of this node would have already been decided, but it may have become a
+ // constant, in which case we'd like to know.
+ if (forNode(node).m_value)
+ m_state.setFoundConstants(true);
break;
case Upsilon: {
m_state.createValueForNode(node->phi());
- AbstractValue& value = forNode(node->child1());
- forNode(node) = value;
- forNode(node->phi()) = value;
+ forNode(node->phi()) = forNode(node->child1());
break;
}
case Call:
case Construct:
- node->setCanExit(true);
+ case NativeCall:
+ case NativeConstruct:
+ case CallVarargs:
+ case CallForwardVarargs:
+ case ConstructVarargs:
+ case ConstructForwardVarargs:
clobberWorld(node->origin.semantic, clobberLimit);
forNode(node).makeHeapTop();
break;
case ForceOSRExit:
- node->setCanExit(true);
+ case CheckBadCell:
m_state.setIsValid(false);
break;
case InvalidationPoint:
- node->setCanExit(true);
+ forAllValues(clobberLimit, AbstractValue::observeInvalidationPointFor);
+ m_state.setStructureClobberState(StructuresAreWatched);
break;
case CheckWatchdogTimer:
- node->setCanExit(true);
break;
case Breakpoint:
case ProfileWillCall:
case ProfileDidCall:
+ case ProfileType:
+ case ProfileControlFlow:
case Phantom:
- case HardPhantom:
- case Check:
case CountExecution:
case CheckTierUpInLoop:
case CheckTierUpAtReturn:
break;
- case StoreBarrier: {
- filter(node->child1(), SpecCell);
+ case Check: {
+ // Simplify out checks that don't actually do checking.
+ for (unsigned i = 0; i < AdjacencyList::Size; ++i) {
+ Edge edge = node->children.child(i);
+ if (!edge)
+ break;
+ if (edge.isProved() || edge.willNotHaveCheck()) {
+ m_state.setFoundConstants(true);
+ break;
+ }
+ }
break;
}
- case StoreBarrierWithNullCheck: {
+ case StoreBarrier: {
+ filter(node->child1(), SpecCell);
break;
}
case CheckTierUpAndOSREnter:
+ case CheckTierUpWithNestedTriggerAndOSREnter:
case LoopHint:
- // We pretend that it can exit because it may want to get all state.
- node->setCanExit(true);
+ case ZombieHint:
break;
- case ZombieHint:
case Unreachable:
case LastNodeType:
case ArithIMul:
case FiatInt52:
- RELEASE_ASSERT_NOT_REACHED();
+ DFG_CRASH(m_graph, node, "Unexpected node type");
break;
}
bool AbstractInterpreter<AbstractStateType>::execute(unsigned indexInBlock)
{
Node* node = m_state.block()->at(indexInBlock);
- if (!startExecuting(node))
- return true;
+ startExecuting();
executeEdges(node);
return executeEffects(indexInBlock, node);
}
template<typename AbstractStateType>
bool AbstractInterpreter<AbstractStateType>::execute(Node* node)
{
- if (!startExecuting(node))
- return true;
-
+ startExecuting();
executeEdges(node);
return executeEffects(UINT_MAX, node);
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::clobberWorld(
- const CodeOrigin& codeOrigin, unsigned clobberLimit)
+ const CodeOrigin&, unsigned clobberLimit)
{
- clobberCapturedVars(codeOrigin);
clobberStructures(clobberLimit);
}
template<typename AbstractStateType>
-void AbstractInterpreter<AbstractStateType>::clobberCapturedVars(const CodeOrigin& codeOrigin)
+template<typename Functor>
+void AbstractInterpreter<AbstractStateType>::forAllValues(
+ unsigned clobberLimit, Functor& functor)
{
- if (codeOrigin.inlineCallFrame) {
- const BitVector& capturedVars = codeOrigin.inlineCallFrame->capturedVars;
- for (size_t i = capturedVars.size(); i--;) {
- if (!capturedVars.quickGet(i))
- continue;
- m_state.variables().local(i).makeHeapTop();
- }
- } else {
- for (size_t i = m_codeBlock->m_numVars; i--;) {
- if (m_codeBlock->isCaptured(virtualRegisterForLocal(i)))
- m_state.variables().local(i).makeHeapTop();
- }
- }
-
- for (size_t i = m_state.variables().numberOfArguments(); i--;) {
- if (m_codeBlock->isCaptured(virtualRegisterForArgument(i)))
- m_state.variables().argument(i).makeHeapTop();
- }
-}
-
-template<typename AbstractStateType>
-void AbstractInterpreter<AbstractStateType>::clobberStructures(unsigned clobberLimit)
-{
- if (!m_state.haveStructures())
- return;
+ SamplingRegion samplingRegion("DFG AI For All Values");
if (clobberLimit >= m_state.block()->size())
clobberLimit = m_state.block()->size();
else
clobberLimit++;
ASSERT(clobberLimit <= m_state.block()->size());
for (size_t i = clobberLimit; i--;)
- forNode(m_state.block()->at(i)).clobberStructures();
+ functor(forNode(m_state.block()->at(i)));
if (m_graph.m_form == SSA) {
HashSet<Node*>::iterator iter = m_state.block()->ssa->liveAtHead.begin();
HashSet<Node*>::iterator end = m_state.block()->ssa->liveAtHead.end();
for (; iter != end; ++iter)
- forNode(*iter).clobberStructures();
+ functor(forNode(*iter));
}
for (size_t i = m_state.variables().numberOfArguments(); i--;)
- m_state.variables().argument(i).clobberStructures();
+ functor(m_state.variables().argument(i));
for (size_t i = m_state.variables().numberOfLocals(); i--;)
- m_state.variables().local(i).clobberStructures();
- m_state.setHaveStructures(true);
+ functor(m_state.variables().local(i));
+}
+
+template<typename AbstractStateType>
+void AbstractInterpreter<AbstractStateType>::clobberStructures(unsigned clobberLimit)
+{
+ SamplingRegion samplingRegion("DFG AI Clobber Structures");
+ forAllValues(clobberLimit, AbstractValue::clobberStructuresFor);
+ setDidClobber();
+}
+
+template<typename AbstractStateType>
+void AbstractInterpreter<AbstractStateType>::observeTransition(
+ unsigned clobberLimit, Structure* from, Structure* to)
+{
+ AbstractValue::TransitionObserver transitionObserver(from, to);
+ forAllValues(clobberLimit, transitionObserver);
+
+ ASSERT(!from->dfgShouldWatch()); // We don't need to claim to be in a clobbered state because 'from' was never watchable (during the time we were compiling), hence no constants ever introduced into the DFG IR that ever had a watchable structure would ever have the same structure as from.
+}
+
+template<typename AbstractStateType>
+void AbstractInterpreter<AbstractStateType>::observeTransitions(
+ unsigned clobberLimit, const TransitionVector& vector)
+{
+ AbstractValue::TransitionsObserver transitionsObserver(vector);
+ forAllValues(clobberLimit, transitionsObserver);
+
+ if (!ASSERT_DISABLED) {
+ // We don't need to claim to be in a clobbered state because none of the Transition::previous structures are watchable.
+ for (unsigned i = vector.size(); i--;)
+ ASSERT(!vector[i].previous->dfgShouldWatch());
+ }
+}
+
+template<typename AbstractStateType>
+void AbstractInterpreter<AbstractStateType>::setDidClobber()
+{
m_state.setDidClobber(true);
+ m_state.setStructureClobberState(StructuresAreClobbered);
+}
+
+template<typename AbstractStateType>
+void AbstractInterpreter<AbstractStateType>::dump(PrintStream& out) const
+{
+ const_cast<AbstractInterpreter<AbstractStateType>*>(this)->dump(out);
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::dump(PrintStream& out)
{
CommaPrinter comma(" ");
+ HashSet<Node*> seen;
if (m_graph.m_form == SSA) {
HashSet<Node*>::iterator iter = m_state.block()->ssa->liveAtHead.begin();
HashSet<Node*>::iterator end = m_state.block()->ssa->liveAtHead.end();
for (; iter != end; ++iter) {
Node* node = *iter;
+ seen.add(node);
AbstractValue& value = forNode(node);
if (value.isClear())
continue;
}
for (size_t i = 0; i < m_state.block()->size(); ++i) {
Node* node = m_state.block()->at(i);
+ seen.add(node);
AbstractValue& value = forNode(node);
if (value.isClear())
continue;
out.print(comma, node, ":", value);
}
+ if (m_graph.m_form == SSA) {
+ HashSet<Node*>::iterator iter = m_state.block()->ssa->liveAtTail.begin();
+ HashSet<Node*>::iterator end = m_state.block()->ssa->liveAtTail.end();
+ for (; iter != end; ++iter) {
+ Node* node = *iter;
+ if (seen.contains(node))
+ continue;
+ AbstractValue& value = forNode(node);
+ if (value.isClear())
+ continue;
+ out.print(comma, node, ":", value);
+ }
+ }
}
template<typename AbstractStateType>
template<typename AbstractStateType>
FiltrationResult AbstractInterpreter<AbstractStateType>::filterByValue(
- AbstractValue& abstractValue, JSValue concreteValue)
+ AbstractValue& abstractValue, FrozenValue concreteValue)
{
if (abstractValue.filterByValue(concreteValue) == FiltrationOK)
return FiltrationOK;