diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h')
-rw-r--r-- | Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h | 2623 |
1 files changed, 1948 insertions, 675 deletions
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h index 3f68aced1..467679b56 100644 --- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h +++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013, 2014 Apple Inc. All rights reserved. + * Copyright (C) 2013-2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -23,15 +23,19 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DFGAbstractInterpreterInlines_h -#define DFGAbstractInterpreterInlines_h - -#include <wtf/Platform.h> +#pragma once #if ENABLE(DFG_JIT) +#include "ArrayConstructor.h" #include "DFGAbstractInterpreter.h" +#include "DOMJITGetterSetter.h" +#include "DOMJITSignature.h" #include "GetByIdStatus.h" +#include "GetterSetter.h" +#include "HashMapImpl.h" +#include "JITOperations.h" +#include "MathCommon.h" #include "Operations.h" #include "PutByIdStatus.h" #include "StringObject.h" @@ -42,8 +46,11 @@ template<typename AbstractStateType> AbstractInterpreter<AbstractStateType>::AbstractInterpreter(Graph& graph, AbstractStateType& state) : m_codeBlock(graph.m_codeBlock) , m_graph(graph) + , m_vm(m_graph.m_vm) , m_state(state) { + if (m_graph.m_form == SSA) + m_phiChildren = std::make_unique<PhiChildren>(m_graph); } template<typename AbstractStateType> @@ -58,17 +65,23 @@ AbstractInterpreter<AbstractStateType>::booleanResult( { JSValue childConst = value.value(); if (childConst) { - if (childConst.toBoolean(m_codeBlock->globalObjectFor(node->codeOrigin)->globalExec())) + if (childConst.toBoolean(m_codeBlock->globalObjectFor(node->origin.semantic)->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) + if (isCellSpeculation(value.m_type) && !value.m_structure.isTop()) { + bool allTrue = true; + for (unsigned i = value.m_structure.size(); i--;) { + RegisteredStructure 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; } @@ -76,40 +89,49 @@ AbstractInterpreter<AbstractStateType>::booleanResult( } 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> void AbstractInterpreter<AbstractStateType>::executeEdges(Node* node) { - DFG_NODE_DO_TO_CHILDREN(m_graph, node, filterEdgeByUse); + m_graph.doToChildren( + node, + [&] (Edge& edge) { + filterEdgeByUse(edge); + }); } template<typename AbstractStateType> -void AbstractInterpreter<AbstractStateType>::executeEdges(unsigned indexInBlock) +void AbstractInterpreter<AbstractStateType>::executeKnownEdgeTypes(Node* node) { - executeEdges(m_state.block()->at(indexInBlock)); + // Some use kinds are required to not have checks, because we know somehow that the incoming + // value will already have the type we want. In those cases, AI may not be smart enough to + // prove that this is indeed the case. But the existance of the edge is enough to prove that + // it is indeed the case. Taking advantage of this is not optional, since otherwise the DFG + // and FTL backends may emit checks in a node that lacks a valid exit origin. + m_graph.doToChildren( + node, + [&] (Edge& edge) { + if (mayHaveTypeCheck(edge.useKind())) + return; + + filterEdgeByUse(edge); + }); } 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> @@ -118,64 +140,94 @@ void AbstractInterpreter<AbstractStateType>::verifyEdges(Node* node) DFG_NODE_DO_TO_CHILDREN(m_graph, node, verifyEdge); } +inline bool isToThisAnIdentity(bool isStrictMode, AbstractValue& valueForNode) +{ + // We look at the type first since that will cover most cases and does not require iterating all the structures. + if (isStrictMode) { + if (valueForNode.m_type && !(valueForNode.m_type & SpecObjectOther)) + return true; + } else { + if (valueForNode.m_type && !(valueForNode.m_type & (~SpecObject | SpecObjectOther))) + return true; + } + + if ((isStrictMode || (valueForNode.m_type && !(valueForNode.m_type & ~SpecObject))) && valueForNode.m_structure.isFinite()) { + bool overridesToThis = false; + valueForNode.m_structure.forEach([&](RegisteredStructure structure) { + TypeInfo type = structure->typeInfo(); + ASSERT(type.isObject() || type.type() == StringType || type.type() == SymbolType); + if (!isStrictMode) + ASSERT(type.isObject()); + // We don't need to worry about strings/symbols here since either: + // 1) We are in strict mode and strings/symbols are not wrapped + // 2) The AI has proven that the type of this is a subtype of object + if (type.isObject() && type.overridesToThis()) + overridesToThis = true; + }); + return !overridesToThis; + } + + return false; +} + template<typename AbstractStateType> bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimit, Node* node) { - if (!ASSERT_DISABLED) - verifyEdges(node); + verifyEdges(node); m_state.createValueForNode(node); switch (node->op()) { case JSConstant: - case WeakJSConstant: - case PhantomArguments: { - forNode(node).set(m_graph, m_graph.valueOfJSConstant(node)); + case DoubleConstant: + case Int52Constant: { + setBuiltInConstant(node, *node->constant()); break; } - - case Identity: { - forNode(node) = forNode(node->child1()); + + case LazyJSConstant: { + LazyJSValue value = node->lazyJSValue(); + switch (value.kind()) { + case LazyJSValue::KnownValue: + setConstant(node, value.value()->value()); + break; + case LazyJSValue::SingleCharacterString: + case LazyJSValue::KnownStringImpl: + case LazyJSValue::NewStringImpl: + forNode(node).setType(m_graph, SpecString); + break; + } 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; + case Identity: { + forNode(node) = forNode(node->child1()); + 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(); - if (variableAccessData->prediction() == SpecNone) { - m_state.setIsValid(false); - break; - } 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; @@ -191,7 +243,12 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi } 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; } @@ -202,10 +259,31 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi 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(SpecInt32Only); + for (unsigned i = data->limit - 1; i--;) + m_state.variables().operand(data->start.offset() + i).makeHeapTop(); break; + } case BitAnd: case BitOr: @@ -213,6 +291,12 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi case BitRShift: case BitLShift: case BitURShift: { + if (node->child1().useKind() == UntypedUse || node->child2().useKind() == UntypedUse) { + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecInt32Only); + break; + } + JSValue left = forNode(node->child1()).value(); JSValue right = forNode(node->child2()).value(); if (left && right && left.isInt32() && right.isInt32()) { @@ -243,19 +327,36 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi } break; } - forNode(node).setType(SpecInt32); + + if (node->op() == BitAnd + && (isBoolInt32Speculation(forNode(node->child1()).m_type) || + isBoolInt32Speculation(forNode(node->child2()).m_type))) { + forNode(node).setType(SpecBoolInt32); + break; + } + + forNode(node).setType(SpecInt32Only); break; } case UInt32ToNumber: { JSValue child = forNode(node->child1()).value(); if (doesOverflow(node->arithMode())) { + if (enableInt52()) { + if (child && child.isAnyInt()) { + int64_t machineInt = child.asAnyInt(); + setConstant(node, jsNumber(static_cast<uint32_t>(machineInt))); + break; + } + forNode(node).setType(SpecAnyInt); + break; + } if (child && child.isInt32()) { uint32_t value = child.asInt32(); setConstant(node, jsNumber(value)); break; } - forNode(node).setType(SpecDouble); + forNode(node).setType(SpecAnyIntAsDouble); break; } if (child && child.isInt32()) { @@ -265,8 +366,27 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } } - forNode(node).setType(SpecInt32); - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); + break; + } + + case BooleanToNumber: { + JSValue concreteValue = forNode(node->child1()).value(); + if (concreteValue) { + if (concreteValue.isBoolean()) + setConstant(node, jsNumber(concreteValue.asBoolean())); + else + setConstant(node, *m_graph.freeze(concreteValue)); + break; + } + AbstractValue& value = forNode(node); + value = forNode(node->child1()); + if (node->child1().useKind() == UntypedUse && !(value.m_type & ~SpecBoolean)) + m_state.setFoundConstants(true); + if (value.m_type & SpecBoolean) { + value.merge(SpecBoolInt32); + value.filter(~SpecBoolean); + } break; } @@ -280,8 +400,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } } - node->setCanExit(true); - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); break; } @@ -296,55 +415,91 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } if (child.isBoolean()) { - setConstant(node, JSValue(child.asBoolean())); + setConstant(node, jsNumber(child.asBoolean())); + break; + } + if (child.isUndefinedOrNull()) { + setConstant(node, jsNumber(0)); break; } } - forNode(node).setType(SpecInt32); + if (isBooleanSpeculation(forNode(node->child1()).m_type)) { + forNode(node).setType(SpecBoolInt32); + break; + } + + forNode(node).setType(SpecInt32Only); break; } - case Int32ToDouble: { + case DoubleRep: { JSValue child = forNode(node->child1()).value(); - if (child && child.isNumber()) { - setConstant(node, JSValue(JSValue::EncodeAsDouble, child.asNumber())); + if (std::optional<double> number = child.toNumberFromPrimitive()) { + setConstant(node, jsDoubleNumber(*number)); + break; + } + + 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; } - if (isInt32Speculation(forNode(node->child1()).m_type)) - forNode(node).setType(SpecDoubleReal); - else - forNode(node).setType(SpecDouble); + + case Int52RepUse: + case NumberUse: + case RealNumberUse: + break; + + default: + RELEASE_ASSERT_NOT_REACHED(); + } + forNode(node).setType(type); + forNode(node).fixTypeForRepresentation(m_graph, node); break; } - case Int52ToDouble: { + case Int52Rep: { JSValue child = forNode(node->child1()).value(); - if (child && child.isNumber()) { + if (child && child.isAnyInt()) { setConstant(node, child); break; } - forNode(node).setType(SpecDouble); + + forNode(node).setType(SpecAnyInt); break; } - case Int52ToValue: { - JSValue child = forNode(node->child1()).value(); - if (child && child.isNumber()) { - setConstant(node, child); + case ValueRep: { + JSValue value = forNode(node->child1()).value(); + if (value) { + setConstant(node, value); break; } - SpeculatedType type = forNode(node->child1()).m_type; - if (type & SpecInt52) - type = (type | SpecInt32 | SpecInt52AsDouble) & ~SpecInt52; - forNode(node).setType(type); + + 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->codeOrigin, clobberLimit); - forNode(node).setType(SpecString | SpecBytecodeNumber); + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecString | SpecBytecodeNumber); + break; + } + + case StrCat: { + forNode(node).setType(m_graph, SpecString); break; } @@ -364,33 +519,26 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } } - forNode(node).setType(SpecInt32); - if (shouldCheckOverflow(node->arithMode())) - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case MachineIntUse: - if (left && right && left.isMachineInt() && right.isMachineInt()) { - JSValue result = jsNumber(left.asMachineInt() + right.asMachineInt()); - if (result.isMachineInt()) { + case Int52RepUse: + if (left && right && left.isAnyInt() && right.isAnyInt()) { + JSValue result = jsNumber(left.asAnyInt() + right.asAnyInt()); + if (result.isAnyInt()) { setConstant(node, result); break; } } - forNode(node).setType(SpecInt52); - if (!forNode(node->child1()).isType(SpecInt32) - || !forNode(node->child2()).isType(SpecInt32)) - node->setCanExit(true); + forNode(node).setType(SpecAnyInt); break; - case NumberUse: + case DoubleRepUse: if (left && right && left.isNumber() && right.isNumber()) { - setConstant(node, jsNumber(left.asNumber() + right.asNumber())); + setConstant(node, jsDoubleNumber(left.asNumber() + right.asNumber())); break; } - if (isFullRealNumberSpeculation(forNode(node->child1()).m_type) - && isFullRealNumberSpeculation(forNode(node->child2()).m_type)) - forNode(node).setType(SpecDoubleReal); - else - forNode(node).setType(SpecDouble); + forNode(node).setType( + typeOfDoubleSum( + forNode(node->child1()).m_type, forNode(node->child2()).m_type)); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -398,10 +546,20 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi } break; } - + + case ArithClz32: { + JSValue operand = forNode(node->child1()).value(); + if (std::optional<double> number = operand.toNumberFromPrimitive()) { + uint32_t value = toUInt32(*number); + setConstant(node, jsNumber(clz32(value))); + break; + } + forNode(node).setType(SpecInt32Only); + break; + } + case MakeRope: { - node->setCanExit(true); - forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get()); + forNode(node).set(m_graph, m_vm.stringStructure.get()); break; } @@ -421,29 +579,30 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } } - forNode(node).setType(SpecInt32); - if (shouldCheckOverflow(node->arithMode())) - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case MachineIntUse: - if (left && right && left.isMachineInt() && right.isMachineInt()) { - JSValue result = jsNumber(left.asMachineInt() - right.asMachineInt()); - if (result.isMachineInt() || !shouldCheckOverflow(node->arithMode())) { + case Int52RepUse: + if (left && right && left.isAnyInt() && right.isAnyInt()) { + JSValue result = jsNumber(left.asAnyInt() - right.asAnyInt()); + if (result.isAnyInt() || !shouldCheckOverflow(node->arithMode())) { setConstant(node, result); break; } } - forNode(node).setType(SpecInt52); - if (!forNode(node->child1()).isType(SpecInt32) - || !forNode(node->child2()).isType(SpecInt32)) - node->setCanExit(true); + forNode(node).setType(SpecAnyInt); break; - case NumberUse: + case DoubleRepUse: if (left && right && left.isNumber() && right.isNumber()) { - setConstant(node, jsNumber(left.asNumber() - right.asNumber())); + setConstant(node, jsDoubleNumber(left.asNumber() - right.asNumber())); break; } - forNode(node).setType(SpecDouble); + forNode(node).setType( + typeOfDoubleDifference( + forNode(node->child1()).m_type, forNode(node->child2()).m_type)); + break; + case UntypedUse: + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecBytecodeNumber); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -472,38 +631,35 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } } - forNode(node).setType(SpecInt32); - if (shouldCheckOverflow(node->arithMode())) - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case MachineIntUse: - if (child && child.isMachineInt()) { + case Int52RepUse: + if (child && child.isAnyInt()) { double doubleResult; if (shouldCheckNegativeZero(node->arithMode())) doubleResult = -child.asNumber(); else doubleResult = 0 - child.asNumber(); JSValue valueResult = jsNumber(doubleResult); - if (valueResult.isMachineInt()) { + if (valueResult.isAnyInt()) { setConstant(node, valueResult); break; } } - forNode(node).setType(SpecInt52); - if (m_state.forNode(node->child1()).couldBeType(SpecInt52)) - node->setCanExit(true); - if (shouldCheckNegativeZero(node->arithMode())) - node->setCanExit(true); + forNode(node).setType(SpecAnyInt); break; - case NumberUse: + case DoubleRepUse: if (child && child.isNumber()) { - setConstant(node, jsNumber(-child.asNumber())); + setConstant(node, jsDoubleNumber(-child.asNumber())); break; } - forNode(node).setType(SpecDouble); + forNode(node).setType( + typeOfDoubleNegation( + forNode(node->child1()).m_type)); break; default: - RELEASE_ASSERT_NOT_REACHED(); + DFG_ASSERT(m_graph, node, node->child1().useKind() == UntypedUse); + forNode(node).setType(SpecBytecodeNumber); break; } break; @@ -528,34 +684,33 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } } - forNode(node).setType(SpecInt32); - if (shouldCheckOverflow(node->arithMode())) - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case MachineIntUse: - if (left && right && left.isMachineInt() && right.isMachineInt()) { + case Int52RepUse: + if (left && right && left.isAnyInt() && right.isAnyInt()) { double doubleResult = left.asNumber() * right.asNumber(); if (!shouldCheckNegativeZero(node->arithMode())) doubleResult += 0; JSValue valueResult = jsNumber(doubleResult); - if (valueResult.isMachineInt()) { + if (valueResult.isAnyInt()) { setConstant(node, valueResult); break; } } - forNode(node).setType(SpecInt52); - node->setCanExit(true); + forNode(node).setType(SpecAnyInt); break; - case NumberUse: + case DoubleRepUse: if (left && right && left.isNumber() && right.isNumber()) { - setConstant(node, jsNumber(left.asNumber() * right.asNumber())); + setConstant(node, jsDoubleNumber(left.asNumber() * right.asNumber())); break; } - if (isFullRealNumberSpeculation(forNode(node->child1()).m_type) - || isFullRealNumberSpeculation(forNode(node->child2()).m_type)) - forNode(node).setType(SpecDoubleReal); - else - forNode(node).setType(SpecDouble); + forNode(node).setType( + typeOfDoubleProduct( + forNode(node->child1()).m_type, forNode(node->child2()).m_type)); + break; + case UntypedUse: + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecBytecodeNumber); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -581,15 +736,20 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } } - forNode(node).setType(SpecInt32); - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case NumberUse: + case DoubleRepUse: if (left && right && left.isNumber() && right.isNumber()) { - setConstant(node, jsNumber(left.asNumber() / right.asNumber())); + setConstant(node, jsDoubleNumber(left.asNumber() / right.asNumber())); break; } - forNode(node).setType(SpecDouble); + forNode(node).setType( + typeOfDoubleQuotient( + forNode(node->child1()).m_type, forNode(node->child2()).m_type)); + break; + case UntypedUse: + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecBytecodeNumber); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -615,15 +775,16 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } } - forNode(node).setType(SpecInt32); - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case NumberUse: + case DoubleRepUse: if (left && right && left.isNumber() && right.isNumber()) { - setConstant(node, jsNumber(fmod(left.asNumber(), right.asNumber()))); + setConstant(node, jsDoubleNumber(fmod(left.asNumber(), right.asNumber()))); break; } - forNode(node).setType(SpecDouble); + forNode(node).setType( + typeOfDoubleBinaryOp( + forNode(node->child1()).m_type, forNode(node->child2()).m_type)); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -641,17 +802,18 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi setConstant(node, jsNumber(std::min(left.asInt32(), right.asInt32()))); break; } - forNode(node).setType(SpecInt32); - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case NumberUse: + case DoubleRepUse: if (left && right && left.isNumber() && right.isNumber()) { double a = left.asNumber(); double b = right.asNumber(); - setConstant(node, jsNumber(a < b ? a : (b <= a ? b : a + b))); + setConstant(node, jsDoubleNumber(a < b ? a : (b <= a ? b : a + b))); break; } - forNode(node).setType(SpecDouble); + forNode(node).setType( + typeOfDoubleMinMax( + forNode(node->child1()).m_type, forNode(node->child2()).m_type)); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -669,17 +831,18 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi setConstant(node, jsNumber(std::max(left.asInt32(), right.asInt32()))); break; } - forNode(node).setType(SpecInt32); - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case NumberUse: + case DoubleRepUse: if (left && right && left.isNumber() && right.isNumber()) { double a = left.asNumber(); double b = right.asNumber(); - setConstant(node, jsNumber(a > b ? a : (b >= a ? b : a + b))); + setConstant(node, jsDoubleNumber(a > b ? a : (b >= a ? b : a + b))); break; } - forNode(node).setType(SpecDouble); + forNode(node).setType( + typeOfDoubleMinMax( + forNode(node->child1()).m_type, forNode(node->child2()).m_type)); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -692,60 +855,129 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi JSValue child = forNode(node->child1()).value(); switch (node->child1().useKind()) { case Int32Use: - if (child && child.isInt32()) { - JSValue result = jsNumber(fabs(child.asNumber())); + if (std::optional<double> number = child.toNumberFromPrimitive()) { + JSValue result = jsNumber(fabs(*number)); if (result.isInt32()) { setConstant(node, result); break; } } - forNode(node).setType(SpecInt32); - node->setCanExit(true); + forNode(node).setType(SpecInt32Only); break; - case NumberUse: - if (child && child.isNumber()) { - setConstant(node, jsNumber(child.asNumber())); + case DoubleRepUse: + if (std::optional<double> number = child.toNumberFromPrimitive()) { + setConstant(node, jsDoubleNumber(fabs(*number))); break; } - forNode(node).setType(SpecDouble); + forNode(node).setType(typeOfDoubleAbs(forNode(node->child1()).m_type)); break; default: - RELEASE_ASSERT_NOT_REACHED(); + DFG_ASSERT(m_graph, node, node->child1().useKind() == UntypedUse); + forNode(node).setType(SpecFullNumber); break; } break; } - - case ArithSqrt: { - JSValue child = forNode(node->child1()).value(); - if (child && child.isNumber()) { - setConstant(node, jsNumber(sqrt(child.asNumber()))); - 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(SpecDouble); + forNode(node).setType(typeOfDoublePow(forNode(node->child1()).m_type, forNode(node->child2()).m_type)); break; } - - case ArithSin: { - JSValue child = forNode(node->child1()).value(); - if (child && child.isNumber()) { - setConstant(node, jsNumber(sin(child.asNumber()))); - break; - } - forNode(node).setType(SpecDouble); + + case ArithRandom: { + forNode(node).setType(m_graph, SpecDoubleReal); break; } - - case ArithCos: { - JSValue child = forNode(node->child1()).value(); - if (child && child.isNumber()) { - setConstant(node, jsNumber(cos(child.asNumber()))); - break; + + case ArithRound: + case ArithFloor: + case ArithCeil: + case ArithTrunc: { + JSValue operand = forNode(node->child1()).value(); + if (std::optional<double> number = operand.toNumberFromPrimitive()) { + double roundedValue = 0; + if (node->op() == ArithRound) + roundedValue = jsRound(*number); + else if (node->op() == ArithFloor) + roundedValue = floor(*number); + else if (node->op() == ArithCeil) + roundedValue = ceil(*number); + else { + ASSERT(node->op() == ArithTrunc); + roundedValue = trunc(*number); + } + + if (node->child1().useKind() == UntypedUse) { + setConstant(node, jsNumber(roundedValue)); + break; + } + 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 (node->child1().useKind() == DoubleRepUse) { + if (producesInteger(node->arithRoundingMode())) + forNode(node).setType(SpecInt32Only); + else if (node->child1().useKind() == DoubleRepUse) + forNode(node).setType(typeOfDoubleRounding(forNode(node->child1()).m_type)); + } else { + DFG_ASSERT(m_graph, node, node->child1().useKind() == UntypedUse); + forNode(node).setType(SpecFullNumber); } - forNode(node).setType(SpecDouble); break; } + case ArithSqrt: + executeDoubleUnaryOpEffects(node, sqrt); + break; + + case ArithFRound: + executeDoubleUnaryOpEffects(node, [](double value) -> double { return static_cast<float>(value); }); + break; + + case ArithSin: + executeDoubleUnaryOpEffects(node, sin); + break; + + case ArithCos: + executeDoubleUnaryOpEffects(node, cos); + break; + + case ArithTan: + executeDoubleUnaryOpEffects(node, tan); + break; + + case ArithLog: + executeDoubleUnaryOpEffects(node, log); + break; + case LogicalNot: { switch (booleanResult(node, forNode(node->child1()))) { case DefinitelyTrue: @@ -755,60 +987,110 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi setConstant(node, jsBoolean(true)); break; default: - switch (node->child1().useKind()) { - case BooleanUse: - case Int32Use: - case NumberUse: - case UntypedUse: - case StringUse: - break; - case ObjectOrOtherUse: - node->setCanExit(true); - break; - default: - RELEASE_ASSERT_NOT_REACHED(); - break; - } forNode(node).setType(SpecBoolean); break; } break; } - + + case MapHash: { + if (JSValue key = forNode(node->child1()).value()) { + if (std::optional<uint32_t> hash = concurrentJSMapHash(key)) { + // Although C++ code uses uint32_t for the hash, the closest type in DFG IR is Int32 + // and that's what MapHash returns. So, we have to cast to int32_t to avoid large + // unsigned values becoming doubles. This casting between signed and unsigned + // happens in the assembly code we emit when we don't constant fold this node. + setConstant(node, jsNumber(static_cast<int32_t>(*hash))); + break; + } + } + forNode(node).setType(SpecInt32Only); + break; + } + + case ToLowerCase: { + forNode(node).setType(m_graph, SpecString); + break; + } + + case LoadFromJSMapBucket: + forNode(node).makeHeapTop(); + break; + + case GetMapBucket: + forNode(node).setType(m_graph, SpecCellOther); + break; + + case IsNonEmptyMapBucket: + forNode(node).setType(SpecBoolean); + break; + + case IsEmpty: case IsUndefined: case IsBoolean: case IsNumber: - case IsString: case IsObject: - case IsFunction: { - node->setCanExit( - node->op() == IsUndefined - && m_graph.masqueradesAsUndefinedWatchpointIsStillValid(node->codeOrigin)); - JSValue child = forNode(node->child1()).value(); - if (child) { + case IsObjectOrNull: + case IsFunction: + case IsCellWithType: + case IsTypedArrayView: { + AbstractValue child = forNode(node->child1()); + if (child.value()) { bool constantWasSet = true; switch (node->op()) { + case IsCellWithType: + setConstant(node, jsBoolean(child.value().isCell() && child.value().asCell()->type() == node->queriedType())); + break; case IsUndefined: setConstant(node, jsBoolean( - child.isCell() - ? child.asCell()->structure()->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node->codeOrigin)) - : 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())); - break; - case IsString: - setConstant(node, jsBoolean(isJSString(child))); + setConstant(node, jsBoolean(child.value().isNumber())); 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; + case IsEmpty: + setConstant(node, jsBoolean(child.value().isEmpty())); + break; + case IsTypedArrayView: + setConstant(node, jsBoolean(child.value().isObject() && isTypedView(child.value().getObject()->classInfo(m_vm)->typedArrayStorageType))); break; default: constantWasSet = false; @@ -817,58 +1099,205 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi 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 IsEmpty: { + if (child.m_type && !(child.m_type & SpecEmpty)) { + setConstant(node, jsBoolean(false)); + constantWasSet = true; + break; + } + + if (child.m_type && !(child.m_type & ~SpecEmpty)) { + setConstant(node, jsBoolean(true)); + constantWasSet = true; + break; + } + + break; + } + 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 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 | SpecProxyObject))) { + setConstant(node, jsBoolean(false)); + constantWasSet = true; + break; + } + break; + case IsCellWithType: + if (!(child.m_type & ~node->speculatedTypeForQuery())) { + setConstant(node, jsBoolean(true)); + constantWasSet = true; + break; + } + if (!(child.m_type & node->speculatedTypeForQuery())) { + setConstant(node, jsBoolean(false)); + constantWasSet = true; + break; + } + break; + + case IsTypedArrayView: + if (!(child.m_type & ~SpecTypedArrayView)) { + setConstant(node, jsBoolean(true)); + constantWasSet = true; + break; + } + if (!(child.m_type & SpecTypedArrayView)) { + setConstant(node, jsBoolean(false)); + constantWasSet = true; + break; + } + break; + + default: + break; + } + if (constantWasSet) + break; + forNode(node).setType(SpecBoolean); break; } case TypeOf: { - VM* vm = m_codeBlock->vm(); JSValue child = forNode(node->child1()).value(); AbstractValue& abstractChild = forNode(node->child1()); if (child) { - JSValue typeString = jsTypeStringForValue(*vm, m_codeBlock->globalObjectFor(node->codeOrigin), child); - setConstant(node, typeString); + JSValue typeString = jsTypeStringForValue(m_vm, m_codeBlock->globalObjectFor(node->origin.semantic), child); + setConstant(node, *m_graph.freeze(typeString)); break; } if (isFullNumberSpeculation(abstractChild.m_type)) { - setConstant(node, vm->smallStrings.numberString()); + setConstant(node, *m_graph.freeze(m_vm.smallStrings.numberString())); break; } if (isStringSpeculation(abstractChild.m_type)) { - setConstant(node, vm->smallStrings.stringString()); + setConstant(node, *m_graph.freeze(m_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 - SpecFunction))) { + setConstant(node, *m_graph.freeze(m_vm.smallStrings.objectString())); break; } if (isFunctionSpeculation(abstractChild.m_type)) { - setConstant(node, vm->smallStrings.functionString()); + setConstant(node, *m_graph.freeze(m_vm.smallStrings.functionString())); break; } if (isBooleanSpeculation(abstractChild.m_type)) { - setConstant(node, vm->smallStrings.booleanString()); + setConstant(node, *m_graph.freeze(m_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(m_vm.smallStrings.symbolString())); break; } - forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get()); + + forNode(node).setType(m_graph, SpecStringIdent); break; } @@ -876,8 +1305,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi case CompareLessEq: case CompareGreater: case CompareGreaterEq: - case CompareEq: - case CompareEqConstant: { + case CompareEq: { JSValue leftConst = forNode(node->child1()).value(); JSValue rightConst = forNode(node->child2()).value(); if (leftConst && rightConst) { @@ -907,90 +1335,198 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; } - if (node->op() == CompareEq && leftConst.isString() && rightConst.isString()) { + if (leftConst.isString() && rightConst.isString()) { const StringImpl* a = asString(leftConst)->tryGetValueImpl(); const StringImpl* b = asString(rightConst)->tryGetValueImpl(); if (a && b) { - setConstant(node, jsBoolean(WTF::equal(a, b))); + bool result; + if (node->op() == CompareEq) + result = WTF::equal(a, b); + else if (node->op() == CompareLess) + result = codePointCompare(a, b) < 0; + else if (node->op() == CompareLessEq) + result = codePointCompare(a, b) <= 0; + else if (node->op() == CompareGreater) + result = codePointCompare(a, b) > 0; + else if (node->op() == CompareGreaterEq) + result = codePointCompare(a, b) >= 0; + else + RELEASE_ASSERT_NOT_REACHED(); + setConstant(node, jsBoolean(result)); break; } } + + if (node->op() == CompareEq && leftConst.isSymbol() && rightConst.isSymbol()) { + setConstant(node, jsBoolean(asSymbol(leftConst) == asSymbol(rightConst))); + break; + } } - if (node->op() == CompareEqConstant || node->op() == CompareEq) { + if (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))) { + if (!valuesCouldBeEqual(leftType, rightType)) { setConstant(node, jsBoolean(false)); break; } + + if (leftType == SpecOther) + std::swap(leftType, rightType); + if (rightType == SpecOther) { + // Undefined and Null are always equal when compared to eachother. + if (!(leftType & ~SpecOther)) { + setConstant(node, jsBoolean(true)); + break; + } + + // Any other type compared to Null or Undefined is always false + // as long as the MasqueradesAsUndefined watchpoint is valid. + // + // MasqueradesAsUndefined only matters for SpecObjectOther, other + // cases are always "false". + if (!(leftType & (SpecObjectOther | SpecOther))) { + setConstant(node, jsBoolean(false)); + break; + } + + if (!(leftType & SpecOther) && m_graph.masqueradesAsUndefinedWatchpointIsStillValid(node->origin.semantic)) { + JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic); + m_graph.watchpoints().addLazily(globalObject->masqueradesAsUndefinedWatchpoint()); + setConstant(node, jsBoolean(false)); + break; + } + } } - forNode(node).setType(SpecBoolean); + if (node->child1() == node->child2()) { + if (node->isBinaryUseKind(Int32Use) || + node->isBinaryUseKind(Int52RepUse) || + node->isBinaryUseKind(StringUse) || + node->isBinaryUseKind(BooleanUse) || + node->isBinaryUseKind(SymbolUse) || + node->isBinaryUseKind(StringIdentUse) || + node->isBinaryUseKind(ObjectUse) || + node->isBinaryUseKind(ObjectUse, ObjectOrOtherUse) || + node->isBinaryUseKind(ObjectOrOtherUse, ObjectUse)) { + switch (node->op()) { + case CompareLess: + case CompareGreater: + setConstant(node, jsBoolean(false)); + break; + case CompareLessEq: + case CompareGreaterEq: + case CompareEq: + setConstant(node, jsBoolean(true)); + break; + default: + DFG_CRASH(m_graph, node, "Unexpected node type"); + break; + } + break; + } + } - // 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); + forNode(node).setType(SpecBoolean); break; } - case CompareStrictEq: - case CompareStrictEqConstant: { + case CompareStrictEq: { Node* leftNode = node->child1().node(); Node* rightNode = node->child2().node(); JSValue left = forNode(leftNode).value(); JSValue right = forNode(rightNode).value(); if (left && right) { - if (left.isNumber() && right.isNumber()) { - setConstant(node, jsBoolean(left.asNumber() == right.asNumber())); - break; - } if (left.isString() && right.isString()) { + // We need this case because JSValue::strictEqual is otherwise too racy for + // string comparisons. const StringImpl* a = asString(left)->tryGetValueImpl(); const StringImpl* b = asString(right)->tryGetValueImpl(); if (a && b) { setConstant(node, jsBoolean(WTF::equal(a, b))); break; } + } else { + setConstant(node, jsBoolean(JSValue::strictEqual(0, left, right))); + break; } } + + SpeculatedType leftLUB = leastUpperBoundOfStrictlyEquivalentSpeculations(forNode(leftNode).m_type); + SpeculatedType rightLUB = leastUpperBoundOfStrictlyEquivalentSpeculations(forNode(rightNode).m_type); + if (!(leftLUB & rightLUB)) { + setConstant(node, jsBoolean(false)); + break; + } + + if (node->child1() == node->child2()) { + if (node->isBinaryUseKind(BooleanUse) || + node->isBinaryUseKind(Int32Use) || + node->isBinaryUseKind(Int52RepUse) || + node->isBinaryUseKind(StringUse) || + node->isBinaryUseKind(StringIdentUse) || + node->isBinaryUseKind(SymbolUse) || + node->isBinaryUseKind(ObjectUse) || + node->isBinaryUseKind(MiscUse, UntypedUse) || + node->isBinaryUseKind(UntypedUse, MiscUse) || + node->isBinaryUseKind(StringIdentUse, NotStringVarUse) || + node->isBinaryUseKind(NotStringVarUse, StringIdentUse) || + node->isBinaryUseKind(StringUse, UntypedUse) || + node->isBinaryUseKind(UntypedUse, StringUse)) { + setConstant(node, jsBoolean(true)); + break; + } + } + + forNode(node).setType(SpecBoolean); + break; + } + + case CompareEqPtr: { + Node* childNode = node->child1().node(); + JSValue childValue = forNode(childNode).value(); + if (childValue) { + setConstant(node, jsBoolean(childValue.isCell() && childValue.asCell() == node->cellOperand()->cell())); + break; + } + forNode(node).setType(SpecBoolean); - node->setCanExit(true); // This is overly conservative. break; } case StringCharCodeAt: - node->setCanExit(true); - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); 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()); + forNode(node).set(m_graph, m_vm.stringStructure.get()); break; case GetByVal: { - node->setCanExit(true); switch (node->arrayMode().type()) { case Array::SelectUsingPredictions: case Array::Unprofiled: - case Array::Undecided: + case Array::SelectUsingArguments: RELEASE_ASSERT_NOT_REACHED(); break; case Array::ForceExit: m_state.setIsValid(false); break; + case Array::Undecided: { + JSValue index = forNode(node->child2()).value(); + if (index && index.isInt32() && index.asInt32() >= 0) { + setConstant(node, jsUndefined()); + break; + } + forNode(node).setType(SpecOther); + break; + } case Array::Generic: - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).makeHeapTop(); break; case Array::String: @@ -1005,27 +1541,28 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi // implies an in-bounds access). None of this feels like it's worth it, // so we're going with TOP for now. The same thing applies to // clobbering the world. - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).makeHeapTop(); } else - forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get()); + forNode(node).set(m_graph, m_vm.stringStructure.get()); break; - case Array::Arguments: + case Array::DirectArguments: + case Array::ScopedArguments: forNode(node).makeHeapTop(); break; case Array::Int32: if (node->arrayMode().isOutOfBounds()) { - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).makeHeapTop(); } else - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); break; case Array::Double: if (node->arrayMode().isOutOfBounds()) { - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).makeHeapTop(); } else if (node->arrayMode().isSaneChain()) - forNode(node).setType(SpecDouble); + forNode(node).setType(SpecBytecodeDouble); else forNode(node).setType(SpecDoubleReal); break; @@ -1033,40 +1570,40 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi case Array::ArrayStorage: case Array::SlowPutArrayStorage: if (node->arrayMode().isOutOfBounds()) - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).makeHeapTop(); break; case Array::Int8Array: - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); break; case Array::Int16Array: - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); break; case Array::Int32Array: - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); break; case Array::Uint8Array: - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); break; case Array::Uint8ClampedArray: - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); break; case Array::Uint16Array: - forNode(node).setType(SpecInt32); + forNode(node).setType(SpecInt32Only); break; case Array::Uint32Array: if (node->shouldSpeculateInt32()) - forNode(node).setType(SpecInt32); - else if (enableInt52() && node->shouldSpeculateMachineInt()) - forNode(node).setType(SpecInt52); + forNode(node).setType(SpecInt32Only); + else if (enableInt52() && node->shouldSpeculateAnyInt()) + forNode(node).setType(SpecAnyInt); else - forNode(node).setType(SpecDouble); + forNode(node).setType(SpecAnyIntAsDouble); break; case Array::Float32Array: - forNode(node).setType(SpecDouble); + forNode(node).setType(SpecFullDouble); break; case Array::Float64Array: - forNode(node).setType(SpecDouble); + forNode(node).setType(SpecFullDouble); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -1078,30 +1615,29 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi case PutByValDirect: case PutByVal: case PutByValAlias: { - node->setCanExit(true); switch (node->arrayMode().modeForPut().type()) { case Array::ForceExit: m_state.setIsValid(false); break; case Array::Generic: - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); break; case Array::Int32: if (node->arrayMode().isOutOfBounds()) - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); break; case Array::Double: if (node->arrayMode().isOutOfBounds()) - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); break; case Array::Contiguous: case Array::ArrayStorage: if (node->arrayMode().isOutOfBounds()) - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); break; case Array::SlowPutArrayStorage: if (node->arrayMode().mayStoreToHole()) - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); break; default: break; @@ -1110,25 +1646,121 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi } case ArrayPush: - node->setCanExit(true); - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).setType(SpecBytecodeNumber); break; + + case ArraySlice: { + JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic); + + // FIXME: We could do better here if we prove that the + // incoming value has only a single structure. + RegisteredStructureSet structureSet; + structureSet.add(m_graph.registerStructure(globalObject->originalArrayStructureForIndexingType(ArrayWithInt32))); + structureSet.add(m_graph.registerStructure(globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous))); + structureSet.add(m_graph.registerStructure(globalObject->originalArrayStructureForIndexingType(ArrayWithDouble))); + + forNode(node).set(m_graph, structureSet); + break; + } case ArrayPop: - node->setCanExit(true); - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).makeHeapTop(); break; + + case GetMyArgumentByVal: + case GetMyArgumentByValOutOfBounds: { + 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, or to return undefined. We could implement that eventually. - case RegExpExec: + unsigned argumentIndex = index.asUInt32() + node->numberOfArgumentsToSkip(); + if (inlineCallFrame) { + if (argumentIndex < inlineCallFrame->arguments.size() - 1) { + forNode(node) = m_state.variables().operand( + virtualRegisterForArgument(argumentIndex + 1) + inlineCallFrame->stackOffset); + m_state.setFoundConstants(true); + break; + } + } else { + if (argumentIndex < m_state.variables().numberOfArguments() - 1) { + forNode(node) = m_state.variables().argument(argumentIndex + 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 = 1 + node->numberOfArgumentsToSkip(); i < inlineCallFrame->arguments.size(); ++i) { + result.merge( + m_state.variables().operand( + virtualRegisterForArgument(i) + inlineCallFrame->stackOffset)); + } + + if (node->op() == GetMyArgumentByValOutOfBounds) + result.merge(SpecOther); + + if (result.value()) + m_state.setFoundConstants(true); + + forNode(node) = result; + break; + } + forNode(node).makeHeapTop(); break; + } + + case RegExpExec: + if (node->child2().useKind() == RegExpObjectUse + && node->child3().useKind() == StringUse) { + // This doesn't clobber the world since there are no conversions to perform. + } else + clobberWorld(node->origin.semantic, clobberLimit); + if (JSValue globalObjectValue = forNode(node->child1()).m_value) { + if (JSGlobalObject* globalObject = jsDynamicCast<JSGlobalObject*>(m_vm, globalObjectValue)) { + if (!globalObject->isHavingABadTime()) { + m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint()); + Structure* structure = globalObject->regExpMatchesArrayStructure(); + m_graph.registerStructure(structure); + forNode(node).set(m_graph, structure); + forNode(node).merge(SpecOther); + break; + } + } + } + forNode(node).setType(m_graph, SpecOther | SpecArray); + break; case RegExpTest: + if (node->child2().useKind() == RegExpObjectUse + && node->child3().useKind() == StringUse) { + // This doesn't clobber the world since there are no conversions to perform. + } else + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).setType(SpecBoolean); break; + case StringReplace: + case StringReplaceRegExp: + if (node->child1().useKind() == StringUse + && node->child2().useKind() == RegExpObjectUse + && node->child3().useKind() == StringUse) { + // This doesn't clobber the world. It just reads and writes regexp state. + } else + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).set(m_graph, m_vm.stringStructure.get()); + break; + case Jump: break; @@ -1147,7 +1779,6 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi // 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; } @@ -1161,11 +1792,18 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi case Return: m_state.setIsValid(false); break; + + case TailCall: + case DirectTailCall: + case TailCallVarargs: + case TailCallForwardVarargs: + clobberWorld(node->origin.semantic, clobberLimit); + m_state.setIsValid(false); + break; case Throw: - case ThrowReferenceError: + case ThrowStaticError: m_state.setIsValid(false); - node->setCanExit(true); break; case ToPrimitive: { @@ -1177,66 +1815,70 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi ASSERT(node->child1().useKind() == UntypedUse); - AbstractValue& source = forNode(node->child1()); - AbstractValue& destination = forNode(node); + if (!(forNode(node->child1()).m_type & ~(SpecFullNumber | SpecBoolean | SpecString | SpecSymbol))) { + m_state.setFoundConstants(true); + forNode(node) = forNode(node->child1()); + break; + } - // NB. The more canonical way of writing this would have been: - // - // destination = source; - // if (destination.m_type & !(SpecFullNumber | SpecString | SpecBoolean)) { - // destination.filter(SpecFullNumber | SpecString | SpecBoolean); - // AbstractValue string; - // string.set(vm->stringStructure); - // destination.merge(string); - // } - // - // The reason why this would, in most other cases, have been better is that - // then destination would preserve any non-SpeculatedType knowledge of source. - // As it stands, the code below forgets any non-SpeculatedType knowledge that - // source would have had. Fortunately, though, for things like strings and - // numbers and booleans, we don't care about the non-SpeculatedType knowedge: - // the structure won't tell us anything we don't already know, and neither - // will ArrayModes. And if the source was a meaningful constant then we - // would have handled that above. Unfortunately, this does mean that - // ToPrimitive will currently forget string constants. But that's not a big - // deal since we don't do any optimization on those currently. - - clobberWorld(node->codeOrigin, clobberLimit); - - SpeculatedType type = source.m_type; - if (type & ~(SpecFullNumber | SpecString | SpecBoolean)) - type = (SpecHeapTop & ~SpecCell) | SpecString; - - destination.setType(type); - if (destination.isClear()) - m_state.setIsValid(false); + clobberWorld(node->origin.semantic, clobberLimit); + + forNode(node).setType(m_graph, SpecHeapTop & ~SpecObject); + break; + } + + case ToNumber: { + JSValue childConst = forNode(node->child1()).value(); + if (childConst && childConst.isNumber()) { + setConstant(node, childConst); + break; + } + + ASSERT(node->child1().useKind() == UntypedUse); + + if (!(forNode(node->child1()).m_type & ~SpecBytecodeNumber)) { + m_state.setFoundConstants(true); + forNode(node) = forNode(node->child1()); + break; + } + + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecBytecodeNumber); break; } - case ToString: { + case ToString: + case CallStringConstructor: { switch (node->child1().useKind()) { case StringObjectUse: // This also filters that the StringObject has the primordial StringObject // structure. filter( node->child1(), - m_graph.globalObjectFor(node->codeOrigin)->stringObjectStructure()); - node->setCanExit(true); // We could be more precise but it's likely not worth it. + m_graph.registerStructure(m_graph.globalObjectFor(node->origin.semantic)->stringObjectStructure())); break; case StringOrStringObjectUse: - node->setCanExit(true); // We could be more precise but it's likely not worth it. + case Int32Use: + case Int52RepUse: + case DoubleRepUse: + case NotCellUse: break; case CellUse: case UntypedUse: - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); break; default: RELEASE_ASSERT_NOT_REACHED(); break; } - forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get()); + forNode(node).set(m_graph, m_vm.stringStructure.get()); break; } + + case NumberToStringWithRadix: + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get()); + break; case NewStringObject: { ASSERT(node->structure()->classInfo() == StringObject::info()); @@ -1245,25 +1887,40 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi } case NewArray: - node->setCanExit(true); forNode(node).set( m_graph, - m_graph.globalObjectFor(node->codeOrigin)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType())); - m_state.setHaveStructures(true); + m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType())); + break; + + case NewArrayWithSpread: + if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) { + // We've compiled assuming we're not having a bad time, so to be consistent + // with StructureRegisterationPhase we must say we produce an original array + // allocation structure. + forNode(node).set( + m_graph, + m_graph.globalObjectFor(node->origin.semantic)->originalArrayStructureForIndexingType(ArrayWithContiguous)); + } else { + forNode(node).set( + m_graph, + m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous)); + } + + break; + + case Spread: + forNode(node).set( + m_graph, m_vm.fixedArrayStructure.get()); break; case NewArrayBuffer: - node->setCanExit(true); forNode(node).set( m_graph, - m_graph.globalObjectFor(node->codeOrigin)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType())); - m_state.setHaveStructures(true); + m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType())); break; case NewArrayWithSize: - node->setCanExit(true); - forNode(node).setType(SpecArray); - m_state.setHaveStructures(true); + forNode(node).setType(m_graph, SpecArray); break; case NewTypedArray: @@ -1271,7 +1928,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi case Int32Use: break; case UntypedUse: - clobberWorld(node->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); break; default: RELEASE_ASSERT_NOT_REACHED(); @@ -1279,21 +1936,26 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi } forNode(node).set( m_graph, - m_graph.globalObjectFor(node->codeOrigin)->typedArrayStructure( + m_graph.globalObjectFor(node->origin.semantic)->typedArrayStructureConcurrently( node->typedArrayType())); - m_state.setHaveStructures(true); break; - + case NewRegexp: - forNode(node).set(m_graph, m_graph.globalObjectFor(node->codeOrigin)->regExpStructure()); - m_state.setHaveStructures(true); + forNode(node).set(m_graph, m_graph.globalObjectFor(node->origin.semantic)->regExpStructure()); break; case ToThis: { AbstractValue& source = forNode(node->child1()); AbstractValue& destination = forNode(node); - - if (m_graph.executableFor(node->codeOrigin)->isStrictMode()) + bool strictMode = m_graph.executableFor(node->origin.semantic)->isStrictMode(); + + if (isToThisAnIdentity(strictMode, source)) { + m_state.setFoundConstants(true); + destination = source; + break; + } + + if (strictMode) destination.makeHeapTop(); else { destination = source; @@ -1303,248 +1965,415 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi } 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()); + ASSERT(!!node->structure().get()); forNode(node).set(m_graph, node->structure()); - m_state.setHaveStructures(true); + break; + + case CallObjectConstructor: { + AbstractValue& source = forNode(node->child1()); + AbstractValue& destination = forNode(node); + + if (!(source.m_type & ~SpecObject)) { + m_state.setFoundConstants(true); + destination = source; + break; + } + + forNode(node).setType(m_graph, SpecObject); + break; + } + + case PhantomNewObject: + case PhantomNewFunction: + case PhantomNewGeneratorFunction: + case PhantomNewAsyncFunction: + case PhantomCreateActivation: + case PhantomDirectArguments: + case PhantomClonedArguments: + case PhantomCreateRest: + case PhantomSpread: + case PhantomNewArrayWithSpread: + case BottomValue: + m_state.setDidClobber(true); // Prevent constant folding. + // This claims to return bottom. + break; + + case PutHint: break; + case MaterializeNewObject: { + forNode(node).set(m_graph, node->structureSet()); + break; + } + case CreateActivation: + case MaterializeCreateActivation: forNode(node).set( - m_graph, m_codeBlock->globalObjectFor(node->codeOrigin)->activationStructure()); - m_state.setHaveStructures(true); + m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->activationStructure()); break; - case FunctionReentryWatchpoint: - case TypedArrayWatchpoint: + case CreateDirectArguments: + forNode(node).set(m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->directArgumentsStructure()); break; - - case CreateArguments: - forNode(node) = forNode(node->child1()); - forNode(node).filter(~SpecEmpty); - forNode(node).merge(SpecArguments); + + case CreateScopedArguments: + forNode(node).set(m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->scopedArgumentsStructure()); break; - case TearOffActivation: - case TearOffArguments: - // Does nothing that is user-visible. + case CreateClonedArguments: + if (!m_graph.isWatchingHavingABadTimeWatchpoint(node)) { + forNode(node).setType(m_graph, SpecObject); + break; + } + forNode(node).set(m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->clonedArgumentsStructure()); break; - case CheckArgumentsNotCreated: - if (isEmptySpeculation( - m_state.variables().operand( - m_graph.argumentsRegisterFor(node->codeOrigin).offset()).m_type)) - m_state.setFoundConstants(true); - else - node->setCanExit(true); + case NewGeneratorFunction: + forNode(node).set( + m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->generatorFunctionStructure()); 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(node).set( - m_graph, jsNumber(node->codeOrigin.inlineCallFrame->arguments.size() - 1)); - } else - forNode(node).setType(SpecInt32); - node->setCanExit( - !isEmptySpeculation( - m_state.variables().operand( - m_graph.argumentsRegisterFor(node->codeOrigin)).m_type)); - break; - - case GetMyArgumentsLengthSafe: - // This potentially clobbers all structures if the arguments object had a getter - // installed on the length property. - clobberWorld(node->codeOrigin, 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 NewAsyncFunction: + forNode(node).set( + m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->asyncFunctionStructure()); + break; + + case NewFunction: + forNode(node).set( + m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->functionStructure()); 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 GetCallee: + if (FunctionExecutable* executable = jsDynamicCast<FunctionExecutable*>(m_vm, 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 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, clobberLimit); - // And the result is unknown. - forNode(node).makeHeapTop(); + case GetArgumentCountIncludingThis: + forNode(node).setType(SpecInt32Only); break; - case NewFunction: { - AbstractValue& value = forNode(node); - value = forNode(node->child1()); + case GetRestLength: + forNode(node).setType(SpecInt32Only); + 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->codeOrigin)->functionStructure()); - 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*>(m_vm, 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. + case GetGlobalObject: { + JSValue child = forNode(node->child1()).value(); + if (child) { + setConstant(node, *m_graph.freeze(JSValue(asObject(child)->globalObject()))); + break; + } + + if (forNode(node->child1()).m_structure.isFinite()) { + JSGlobalObject* globalObject = nullptr; + bool ok = true; + forNode(node->child1()).m_structure.forEach( + [&] (RegisteredStructure structure) { + if (!globalObject) + globalObject = structure->globalObject(); + else if (globalObject != structure->globalObject()) + ok = false; + }); + if (globalObject && ok) { + setConstant(node, *m_graph.freeze(JSValue(globalObject))); + break; + } + } + + forNode(node).setType(m_graph, SpecObjectOther); break; + } case GetClosureVar: - forNode(node).makeHeapTop(); + if (JSValue value = m_graph.tryGetConstantClosureVar(forNode(node->child1()), node->scopeOffset())) { + setConstant(node, *m_graph.freeze(value)); + break; + } + forNode(node).makeBytecodeTop(); break; case PutClosureVar: - clobberCapturedVars(node->codeOrigin); break; - + + case GetRegExpObjectLastIndex: + forNode(node).makeHeapTop(); + break; + + case SetRegExpObjectLastIndex: + case RecordRegExpCachedResult: + break; + + case GetFromArguments: + forNode(node).makeHeapTop(); + break; + + case PutToArguments: + break; + + case GetArgument: + forNode(node).makeHeapTop(); + break; + + case TryGetById: + // FIXME: This should constant fold at least as well as the normal GetById case. + // https://bugs.webkit.org/show_bug.cgi?id=156422 + forNode(node).makeHeapTop(); + 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()) { - // 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()); - - if (status.specificValue()) - setConstant(node, status.specificValue()); - else - forNode(node).makeHeapTop(); - filter(node->child1(), status.structureSet()); - - m_state.setFoundConstants(true); - m_state.setHaveStructures(true); - break; + + AbstractValue& value = forNode(node->child1()); + if (value.m_structure.isFinite() + && (node->child1().useKind() == CellUse || !(value.m_type & ~SpecCell))) { + UniquedStringImpl* uid = m_graph.identifiers()[node->identifierNumber()]; + GetByIdStatus status = GetByIdStatus::computeFor(value.m_structure.toStructureSet(), uid); + 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--;) { + // This thing won't give us a variant that involves prototypes. If it did, we'd + // have more work to do here. + DFG_ASSERT(m_graph, node, status[i].conditionSet().isEmpty()); + + result.merge( + m_graph.inferredValueForProperty( + value, uid, status[i].offset(), m_state.structureClobberState())); } + m_state.setFoundConstants(true); + forNode(node) = result; + break; } } - clobberWorld(node->codeOrigin, clobberLimit); + + 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. - forNode(node).setType(SpecInt32); + } + + case GetByValWithThis: + case GetByIdWithThis: + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).makeHeapTop(); 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); + + 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(SpecInt32Only); break; } + case DeleteById: + case DeleteByVal: { + // FIXME: This could decide if the delete will be successful based on the set of structures that + // we get from our base value. https://bugs.webkit.org/show_bug.cgi?id=156611 + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(SpecBoolean); + 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)) { + const RegisteredStructureSet& set = node->structureSet(); + + // 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); - 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()) { - m_state.setFoundConstants(true); - filter(value, value.m_futurePossibleStructure.singleton()); + SpeculatedType admittedTypes = SpecNone; + switch (node->child1().useKind()) { + case CellUse: + case KnownCellUse: + admittedTypes = SpecNone; + break; + case CellOrOtherUse: + admittedTypes = SpecOther; + break; + default: + DFG_CRASH(m_graph, node, "Bad use kind"); break; } - - filter(value, set); + + filter(value, set, admittedTypes); 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); + const RegisteredStructureSet& set = node->structureSet(); + + if (value.value()) { + if (Structure* structure = jsDynamicCast<Structure*>(m_vm, value.value())) { + if (set.contains(m_graph.registerStructure(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*>(m_vm)) { + if (set.contains(m_graph.registerStructure(structure))) + return; + } + allGood = false; + }); + if (allGood) { + m_state.setFoundConstants(true); + break; + } + } + + if (RegisteredStructure structure = set.onlyStructure()) { + filterByValue(node->child1(), *m_graph.freeze(structure.get())); + 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: case AllocatePropertyStorage: case ReallocatePropertyStorage: + case NukeStructureAndSetButterfly: + // FIXME: We don't model the fact that the structureID is nuked, simply because currently + // nobody would currently benefit from having that information. But it's a bug nonetheless. forNode(node).clear(); // The result is not a JS value. break; + case CheckDOM: { + JSValue constant = forNode(node->child1()).value(); + if (constant) { + if (constant.isCell() && constant.asCell()->inherits(m_vm, node->classInfo())) { + m_state.setFoundConstants(true); + ASSERT(constant); + break; + } + } + + AbstractValue& value = forNode(node->child1()); + + if (value.m_structure.isSubClassOf(node->classInfo())) + m_state.setFoundConstants(true); + + filterClassInfo(value, node->classInfo()); + break; + } + case CallDOMGetter: { + CallDOMGetterData* callDOMGetterData = node->callDOMGetterData(); + DOMJIT::CallDOMGetterPatchpoint* patchpoint = callDOMGetterData->patchpoint; + if (patchpoint->effect.writes) + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, callDOMGetterData->domJIT->resultType()); + break; + } + case CallDOM: { + const DOMJIT::Signature* signature = node->signature(); + if (signature->effect.writes) + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, signature->result); + break; + } case CheckArray: { if (node->arrayMode().alreadyChecked(m_graph, node, forNode(node->child1()))) { 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); @@ -1552,11 +2381,15 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi case Array::Int32: case Array::Double: case Array::Contiguous: + case Array::Undecided: 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); @@ -1585,12 +2418,14 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi case Array::Float64Array: filter(node->child1(), SpecFloat64Array); break; + case Array::AnyTypedArray: + filter(node->child1(), SpecTypedArrayView); + break; default: RELEASE_ASSERT_NOT_REACHED(); break; } filterArrayModes(node->child1(), node->arrayMode().arrayModesThatPassFiltering()); - m_state.setHaveStructures(true); break; } case Arrayify: { @@ -1598,59 +2433,269 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi 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(RegisteredStructureSet(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: { - forNode(node).setType(SpecInt32); + JSArrayBufferView* view = m_graph.tryGetFoldableView(forNode(node->child1()).m_value); + if (view) { + setConstant(node, jsNumber(view->byteOffset())); + break; + } + forNode(node).setType(SpecInt32Only); break; } case GetByOffset: { - forNode(node).makeHeapTop(); + StorageAccessData& data = node->storageAccessData(); + UniquedStringImpl* uid = m_graph.identifiers()[data.identifierNumber]; + + // FIXME: The part of this that handles inferred property types relies on AI knowing the structure + // right now. That's probably not optimal. In some cases, we may perform an optimization (usually + // by something other than AI, maybe by CSE for example) that obscures AI's view of the structure + // at the point where GetByOffset runs. Currently, when that happens, we'll have to rely entirely + // on the type that ByteCodeParser was able to prove. + AbstractValue value = m_graph.inferredValueForProperty( + forNode(node->child2()), uid, data.offset, m_state.structureClobberState()); + + // It's possible that the type that ByteCodeParser came up with is better. + AbstractValue typeFromParsing; + typeFromParsing.set(m_graph, data.inferredType, m_state.structureClobberState()); + value.filter(typeFromParsing); + + // If we decide that there does not exist any value that this can return, then it's probably + // because the compilation was already invalidated. + if (value.isClear()) + m_state.setIsValid(false); + + forNode(node) = value; + if (value.m_value) + m_state.setFoundConstants(true); + break; + } + + case GetGetterSetterByOffset: { + StorageAccessData& data = node->storageAccessData(); + JSValue result = m_graph.tryGetConstantProperty(forNode(node->child2()), data.offset); + if (result && jsDynamicCast<GetterSetter*>(m_vm, result)) { + setConstant(node, *m_graph.freeze(result)); + break; + } + + forNode(node).set(m_graph, m_graph.globalObjectFor(node->origin.semantic)->getterSetterStructure()); + break; + } + + case MultiGetByOffset: { + // 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); + + UniquedStringImpl* uid = m_graph.identifiers()[node->multiGetByOffsetData().identifierNumber]; + + AbstractValue base = forNode(node->child1()); + RegisteredStructureSet baseSet; + AbstractValue result; + for (const MultiGetByOffsetCase& getCase : node->multiGetByOffsetData().cases) { + RegisteredStructureSet set = getCase.set(); + set.filter(base); + if (set.isEmpty()) + continue; + baseSet.merge(set); + + switch (getCase.method().kind()) { + case GetByOffsetMethod::Constant: { + AbstractValue thisResult; + thisResult.set( + m_graph, + *getCase.method().constant(), + m_state.structureClobberState()); + result.merge(thisResult); + break; + } + + case GetByOffsetMethod::Load: { + result.merge( + m_graph.inferredValueForProperty( + set, uid, m_state.structureClobberState())); + break; + } + + default: { + result.makeHeapTop(); + break; + } } + } + + if (forNode(node->child1()).changeStructure(m_graph, baseSet) == Contradiction) + m_state.setIsValid(false); + + forNode(node) = result; break; } case PutByOffset: { break; } + + case MultiPutByOffset: { + RegisteredStructureSet newSet; + TransitionVector transitions; + + // 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()); + AbstractValue originalValue = forNode(node->child2()); + AbstractValue resultingValue; + + for (unsigned i = node->multiPutByOffsetData().variants.size(); i--;) { + const PutByIdVariant& variant = node->multiPutByOffsetData().variants[i]; + RegisteredStructureSet thisSet = *m_graph.addStructureSet(variant.oldStructure()); + thisSet.filter(base); + if (thisSet.isEmpty()) + continue; + + AbstractValue thisValue = originalValue; + thisValue.filter(m_graph, variant.requiredType()); + resultingValue.merge(thisValue); - case CheckFunction: { + if (variant.kind() == PutByIdVariant::Transition) { + RegisteredStructure newStructure = m_graph.registerStructure(variant.newStructure()); + if (thisSet.onlyStructure() != newStructure) { + transitions.append( + Transition(m_graph.registerStructure(variant.oldStructureForTransition()), newStructure)); + } // else this is really a replace. + newSet.add(newStructure); + } else { + ASSERT(variant.kind() == PutByIdVariant::Replace); + newSet.merge(thisSet); + } + } + + observeTransitions(clobberLimit, transitions); + if (forNode(node->child1()).changeStructure(m_graph, newSet) == Contradiction) + m_state.setIsValid(false); + forNode(node->child2()) = resultingValue; + if (!!originalValue && !resultingValue) + m_state.setIsValid(false); + break; + } + + case GetExecutable: { + JSValue value = forNode(node->child1()).value(); + if (value) { + JSFunction* function = jsDynamicCast<JSFunction*>(m_vm, value); + if (function) { + setConstant(node, *m_graph.freeze(function->executable())); + break; + } + } + forNode(node).setType(m_graph, SpecCellOther); + break; + } + + 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 CheckStringIdent: { + AbstractValue& value = forNode(node->child1()); + UniquedStringImpl* uid = node->uidOperand(); + ASSERT(!(value.m_type & ~SpecStringIdent)); // Edge filtering should have already ensured this. + + JSValue childConstant = value.value(); + if (childConstant) { + ASSERT(childConstant.isString()); + if (asString(childConstant)->tryGetValueImpl() == uid) { + m_state.setFoundConstants(true); + break; + } + } + + filter(value, SpecStringIdent); + break; + } + case CheckInBounds: { JSValue left = forNode(node->child1()).value(); JSValue right = forNode(node->child2()).value(); @@ -1659,79 +2704,191 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi m_state.setFoundConstants(true); break; } - - node->setCanExit(true); break; } case PutById: - case PutByIdDirect: - node->setCanExit(true); - if (Structure* structure = forNode(node->child1()).bestProvenStructure()) { + case PutByIdFlush: + case PutByIdDirect: { + AbstractValue& value = forNode(node->child1()); + if (value.m_structure.isFinite()) { PutByIdStatus status = PutByIdStatus::computeFor( - m_graph.m_vm, - m_graph.globalObjectFor(node->codeOrigin), - structure, + m_graph.globalObjectFor(node->origin.semantic), + value.m_structure.toStructureSet(), m_graph.identifiers()[node->identifierNumber()], node->op() == PutByIdDirect); - if (status.isSimpleReplace()) { - filter(node->child1(), structure); - m_state.setFoundConstants(true); - m_state.setHaveStructures(true); - break; - } - if (status.isSimpleTransition()) { - clobberStructures(clobberLimit); - forNode(node->child1()).set(m_graph, status.newStructure()); - m_state.setHaveStructures(true); - m_state.setFoundConstants(true); + + if (status.isSimple()) { + RegisteredStructureSet newSet; + TransitionVector transitions; + + for (unsigned i = status.numVariants(); i--;) { + const PutByIdVariant& variant = status[i]; + if (variant.kind() == PutByIdVariant::Transition) { + RegisteredStructure newStructure = m_graph.registerStructure(variant.newStructure()); + transitions.append( + Transition( + m_graph.registerStructure(variant.oldStructureForTransition()), newStructure)); + newSet.add(newStructure); + } else { + ASSERT(variant.kind() == PutByIdVariant::Replace); + newSet.merge(*m_graph.addStructureSet(variant.oldStructure())); + } + } + + if (status.numVariants() == 1 || isFTL(m_graph.m_plan.mode)) + m_state.setFoundConstants(true); + + observeTransitions(clobberLimit, transitions); + if (forNode(node->child1()).changeStructure(m_graph, newSet) == Contradiction) + m_state.setIsValid(false); break; } } - clobberWorld(node->codeOrigin, clobberLimit); + + clobberWorld(node->origin.semantic, clobberLimit); + break; + } + + case PutByValWithThis: + case PutByIdWithThis: + clobberWorld(node->origin.semantic, clobberLimit); + break; + + case PutGetterById: + case PutSetterById: + case PutGetterSetterById: + case PutGetterByVal: + case PutSetterByVal: { + clobberWorld(node->origin.semantic, clobberLimit); + break; + } + + case DefineDataProperty: + case DefineAccessorProperty: + 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->codeOrigin, clobberLimit); + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).setType(SpecBoolean); break; + } + + case HasOwnProperty: { + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(SpecBoolean); + break; + } + case GetEnumerableLength: { + forNode(node).setType(SpecInt32Only); + 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); + + case GetGlobalLexicalVariable: + forNode(node).makeBytecodeTop(); break; - - case PutGlobalVar: + + case GetDynamicVar: + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).makeBytecodeTop(); + break; + + case PutDynamicVar: + clobberWorld(node->origin.semantic, clobberLimit); + break; + + case ResolveScope: + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecObject); + break; + + case PutGlobalVariable: case NotifyWrite: break; - case CheckHasInstance: - node->setCanExit(true); - // Sadly, we don't propagate the fact that we've done CheckHasInstance + case OverridesHasInstance: + forNode(node).setType(SpecBoolean); break; case InstanceOf: - node->setCanExit(true); - // Again, sadly, we don't propagate the fact that we've done InstanceOf + // Sadly, we don't propagate the fact that we've done InstanceOf + forNode(node).setType(SpecBoolean); + break; + + case InstanceOfCustom: + clobberWorld(node->origin.semantic, clobberLimit); forNode(node).setType(SpecBoolean); break; case Phi: RELEASE_ASSERT(m_graph.m_form == SSA); - // The state of this node would have already been decided. + forNode(node) = forNode(NodeFlowProjection(node, NodeFlowProjection::Shadow)); + // 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; + NodeFlowProjection shadow(node->phi(), NodeFlowProjection::Shadow); + if (shadow.isStillValid()) { + m_state.createValueForNode(shadow); + forNode(shadow) = forNode(node->child1()); + } break; } @@ -1740,62 +2897,125 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi break; case Call: + case TailCallInlinedCaller: case Construct: - node->setCanExit(true); - clobberWorld(node->codeOrigin, clobberLimit); + case CallVarargs: + case CallForwardVarargs: + case TailCallVarargsInlinedCaller: + case ConstructVarargs: + case ConstructForwardVarargs: + case TailCallForwardVarargsInlinedCaller: + case CallEval: + case DirectCall: + case DirectConstruct: + case DirectTailCallInlinedCaller: + 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); + case LogShadowChickenPrologue: + case LogShadowChickenTail: break; - case Breakpoint: - case ProfileWillCall: - case ProfileDidCall: + case ProfileType: + case ProfileControlFlow: case Phantom: - case Check: case CountExecution: case CheckTierUpInLoop: case CheckTierUpAtReturn: + case CheckTypeInfoFlags: break; - case ConditionalStoreBarrier: { - if (!needsTypeCheck(node->child2().node(), ~SpecCell)) - m_state.setFoundConstants(true); - filter(node->child1(), SpecCell); + case ParseInt: { + AbstractValue value = forNode(node->child1()); + if (value.m_type && !(value.m_type & ~SpecInt32Only)) { + JSValue radix; + if (!node->child2()) + radix = jsNumber(0); + else + radix = forNode(node->child2()).m_value; + + if (radix.isNumber() + && (radix.asNumber() == 0 || radix.asNumber() == 10)) { + m_state.setFoundConstants(true); + forNode(node).setType(SpecInt32Only); + break; + } + } + + if (node->child1().useKind() == UntypedUse) + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecBytecodeNumber); break; } - case StoreBarrier: { - filter(node->child1(), SpecCell); + case CreateRest: + if (!m_graph.isWatchingHavingABadTimeWatchpoint(node)) { + // This means we're already having a bad time. + clobberWorld(node->origin.semantic, clobberLimit); + forNode(node).setType(m_graph, SpecArray); + break; + } + forNode(node).set( + m_graph, + m_graph.globalObjectFor(node->origin.semantic)->restParameterStructure()); + break; + + 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 SetFunctionName: { + clobberWorld(node->origin.semantic, clobberLimit); break; } + case StoreBarrier: + case FencedStoreBarrier: { + filter(node->child1(), SpecCell); + break; + } + case CheckTierUpAndOSREnter: case LoopHint: - // We pretend that it can exit because it may want to get all state. - node->setCanExit(true); + case ZombieHint: + case ExitOK: break; - case ZombieHint: case Unreachable: + // It may be that during a previous run of AI we proved that something was unreachable, but + // during this run of AI we forget that it's unreachable. AI's proofs don't have to get + // monotonically stronger over time. So, we don't assert that AI doesn't reach the + // Unreachable. We have no choice but to take our past proof at face value. Otherwise we'll + // crash whenever AI fails to be as powerful on run K as it was on run K-1. + m_state.setIsValid(false); + break; + case LastNodeType: case ArithIMul: - RELEASE_ASSERT_NOT_REACHED(); + case FiatInt52: + DFG_CRASH(m_graph, node, "Unexpected node type"); break; } @@ -1812,9 +3032,8 @@ template<typename AbstractStateType> 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); } @@ -1822,79 +3041,99 @@ bool AbstractInterpreter<AbstractStateType>::execute(unsigned indexInBlock) 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) -{ - 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) +template<typename Functor> +void AbstractInterpreter<AbstractStateType>::forAllValues( + unsigned clobberLimit, Functor& functor) { - if (!m_state.haveStructures()) - return; 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(); + for (size_t i = clobberLimit; i--;) { + NodeFlowProjection::forEach( + m_state.block()->at(i), + [&] (NodeFlowProjection nodeProjection) { + functor(forNode(nodeProjection)); + }); + } 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(); + for (NodeFlowProjection node : m_state.block()->ssa->liveAtHead) { + if (node.isStillValid()) + functor(forNode(node)); + } } 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) +{ + forAllValues(clobberLimit, AbstractValue::clobberStructuresFor); + setDidClobber(); +} + +template<typename AbstractStateType> +void AbstractInterpreter<AbstractStateType>::observeTransition( + unsigned clobberLimit, RegisteredStructure from, RegisteredStructure 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<NodeFlowProjection> 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; + for (NodeFlowProjection node : m_state.block()->ssa->liveAtHead) { + seen.add(node); AbstractValue& value = forNode(node); if (value.isClear()) continue; @@ -1902,19 +3141,32 @@ void AbstractInterpreter<AbstractStateType>::dump(PrintStream& out) } } for (size_t i = 0; i < m_state.block()->size(); ++i) { - Node* node = m_state.block()->at(i); - AbstractValue& value = forNode(node); - if (value.isClear()) - continue; - out.print(comma, node, ":", value); + NodeFlowProjection::forEach( + m_state.block()->at(i), [&] (NodeFlowProjection nodeProjection) { + seen.add(nodeProjection); + AbstractValue& value = forNode(nodeProjection); + if (value.isClear()) + return; + out.print(comma, nodeProjection, ":", value); + }); + } + if (m_graph.m_form == SSA) { + for (NodeFlowProjection node : m_state.block()->ssa->liveAtTail) { + if (seen.contains(node)) + continue; + AbstractValue& value = forNode(node); + if (value.isClear()) + continue; + out.print(comma, node, ":", value); + } } } template<typename AbstractStateType> FiltrationResult AbstractInterpreter<AbstractStateType>::filter( - AbstractValue& value, const StructureSet& set) + AbstractValue& value, const RegisteredStructureSet& set, SpeculatedType admittedTypes) { - if (value.filter(m_graph, set) == FiltrationOK) + if (value.filter(m_graph, set, admittedTypes) == FiltrationOK) return FiltrationOK; m_state.setIsValid(false); return Contradiction; @@ -1942,7 +3194,7 @@ FiltrationResult AbstractInterpreter<AbstractStateType>::filter( template<typename AbstractStateType> FiltrationResult AbstractInterpreter<AbstractStateType>::filterByValue( - AbstractValue& abstractValue, JSValue concreteValue) + AbstractValue& abstractValue, FrozenValue concreteValue) { if (abstractValue.filterByValue(concreteValue) == FiltrationOK) return FiltrationOK; @@ -1950,9 +3202,30 @@ FiltrationResult AbstractInterpreter<AbstractStateType>::filterByValue( return Contradiction; } -} } // namespace JSC::DFG +template<typename AbstractStateType> +FiltrationResult AbstractInterpreter<AbstractStateType>::filterClassInfo( + AbstractValue& value, const ClassInfo* classInfo) +{ + if (value.filterClassInfo(m_graph, classInfo) == FiltrationOK) + return FiltrationOK; + m_state.setIsValid(false); + return Contradiction; +} -#endif // ENABLE(DFG_JIT) +template<typename AbstractStateType> +void AbstractInterpreter<AbstractStateType>::executeDoubleUnaryOpEffects(Node* node, double(*equivalentFunction)(double)) +{ + JSValue child = forNode(node->child1()).value(); + if (std::optional<double> number = child.toNumberFromPrimitive()) { + setConstant(node, jsDoubleNumber(equivalentFunction(*number))); + return; + } + SpeculatedType type = SpecFullNumber; + if (node->child1().useKind() == DoubleRepUse) + type = typeOfDoubleUnaryOp(forNode(node->child1()).m_type); + forNode(node).setType(type); +} -#endif // DFGAbstractInterpreterInlines_h +} } // namespace JSC::DFG +#endif // ENABLE(DFG_JIT) |