/* * Copyright (C) 2013-2018 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "DFGAbstractValue.h" #if ENABLE(DFG_JIT) #include "DFGGraph.h" #include "JSCJSValueInlines.h" #include "TrackedReferences.h" namespace JSC { namespace DFG { void AbstractValue::observeTransitions(const TransitionVector& vector) { if (m_type & SpecCell) { m_structure.observeTransitions(vector); ArrayModes newModes = 0; for (unsigned i = vector.size(); i--;) { if (m_arrayModes & arrayModesFromStructure(vector[i].previous.get())) newModes |= arrayModesFromStructure(vector[i].next.get()); } m_arrayModes |= newModes; } checkConsistency(); } void AbstractValue::set(Graph& graph, const FrozenValue& value, StructureClobberState clobberState) { if (!!value && value.value().isCell()) { Structure* structure = value.structure(); StructureRegistrationResult result; RegisteredStructure registeredStructure = graph.registerStructure(structure, result); if (result == StructureRegisteredAndWatched) { m_structure = registeredStructure; if (clobberState == StructuresAreClobbered) { m_arrayModes = ALL_ARRAY_MODES; m_structure.clobber(); } else m_arrayModes = arrayModesFromStructure(structure); } else { m_structure.makeTop(); m_arrayModes = ALL_ARRAY_MODES; } } else { m_structure.clear(); m_arrayModes = 0; } m_type = speculationFromValue(value.value()); m_value = value.value(); checkConsistency(); assertIsRegistered(graph); } void AbstractValue::set(Graph& graph, Structure* structure) { set(graph, graph.registerStructure(structure)); } void AbstractValue::set(Graph& graph, RegisteredStructure structure) { RELEASE_ASSERT(structure); m_structure = structure; m_arrayModes = arrayModesFromStructure(structure.get()); m_type = speculationFromStructure(structure.get()); m_value = JSValue(); checkConsistency(); assertIsRegistered(graph); } void AbstractValue::set(Graph& graph, const RegisteredStructureSet& set) { m_structure = set; m_arrayModes = set.arrayModesFromStructures(); m_type = set.speculationFromStructures(); m_value = JSValue(); checkConsistency(); assertIsRegistered(graph); } void AbstractValue::setType(Graph& graph, SpeculatedType type) { SpeculatedType cellType = type & SpecCell; if (cellType) { if (!(cellType & ~SpecString)) m_structure = graph.stringStructure; else if (isSymbolSpeculation(cellType)) m_structure = graph.symbolStructure; else m_structure.makeTop(); m_arrayModes = ALL_ARRAY_MODES; } else { m_structure.clear(); m_arrayModes = 0; } m_type = type; m_value = JSValue(); checkConsistency(); } void AbstractValue::fixTypeForRepresentation(Graph& graph, NodeFlags representation, Node* node) { if (representation == NodeResultDouble) { if (m_value) { DFG_ASSERT(graph, node, m_value.isNumber()); if (m_value.isInt32()) m_value = jsDoubleNumber(m_value.asNumber()); } if (m_type & SpecIntAnyFormat) { m_type &= ~SpecIntAnyFormat; m_type |= SpecAnyIntAsDouble; } if (m_type & ~SpecFullDouble) DFG_CRASH(graph, node, toCString("Abstract value ", *this, " for double node has type outside SpecFullDouble.\n").data()); } else if (representation == NodeResultInt52) { if (m_type & SpecAnyIntAsDouble) { // AnyIntAsDouble can produce i32 or i52. SpecAnyIntAsDouble doesn't bound the magnitude of the value. m_type &= ~SpecAnyIntAsDouble; m_type |= SpecInt52Any; } if (m_type & SpecInt32Only) { m_type &= ~SpecInt32Only; m_type |= SpecInt32AsInt52; } if (m_type & ~SpecInt52Any) DFG_CRASH(graph, node, toCString("Abstract value ", *this, " for int52 node has type outside SpecInt52Any.\n").data()); if (m_value) { DFG_ASSERT(graph, node, m_value.isAnyInt()); m_type = int52AwareSpeculationFromValue(m_value); } } else { if (m_type & SpecInt32AsInt52) { m_type &= ~SpecInt32AsInt52; m_type |= SpecInt32Only; } if (m_type & SpecNonInt32AsInt52) { m_type &= ~SpecNonInt32AsInt52; m_type |= SpecAnyIntAsDouble; } if (m_type & ~SpecBytecodeTop) DFG_CRASH(graph, node, toCString("Abstract value ", *this, " for value node has type outside SpecBytecodeTop.\n").data()); } checkConsistency(); } void AbstractValue::fixTypeForRepresentation(Graph& graph, Node* node) { fixTypeForRepresentation(graph, node->result(), node); } bool AbstractValue::mergeOSREntryValue(Graph& graph, JSValue value, VariableAccessData* variable, Node* node) { FlushFormat flushFormat = variable->flushFormat(); { if (flushFormat == FlushedDouble && value.isNumber()) value = jsDoubleNumber(value.asNumber()); SpeculatedType incomingType = resultFor(flushFormat) == NodeResultInt52 ? int52AwareSpeculationFromValue(value) : speculationFromValue(value); SpeculatedType requiredType = typeFilterFor(flushFormat); if (incomingType & ~requiredType) return false; } AbstractValue oldMe = *this; if (isClear()) { FrozenValue* frozenValue = graph.freeze(value); if (frozenValue->pointsToHeap()) { m_structure = graph.registerStructure(frozenValue->structure()); m_arrayModes = arrayModesFromStructure(frozenValue->structure()); } else { m_structure.clear(); m_arrayModes = 0; } m_type = speculationFromValue(value); m_value = value; } else { mergeSpeculation(m_type, speculationFromValue(value)); if (!!value && value.isCell()) { RegisteredStructure structure = graph.registerStructure(value.asCell()->structure(graph.m_vm)); mergeArrayModes(m_arrayModes, arrayModesFromStructure(structure.get())); m_structure.merge(RegisteredStructureSet(structure)); } if (m_value != value) m_value = JSValue(); } assertIsRegistered(graph); fixTypeForRepresentation(graph, resultFor(flushFormat), node); checkConsistency(); return oldMe != *this; } FiltrationResult AbstractValue::filter( Graph& graph, const RegisteredStructureSet& other, SpeculatedType admittedTypes) { ASSERT(!(admittedTypes & SpecCell)); if (isClear()) return FiltrationOK; // FIXME: This could be optimized for the common case of m_type not // having structures, array modes, or a specific value. // https://bugs.webkit.org/show_bug.cgi?id=109663 m_type &= other.speculationFromStructures() | admittedTypes; m_arrayModes &= other.arrayModesFromStructures(); m_structure.filter(other); // It's possible that prior to the above two statements we had (Foo, TOP), where // Foo is a SpeculatedType that is disjoint with the passed RegisteredStructureSet. In that // case, we will now have (None, [someStructure]). In general, we need to make // sure that new information gleaned from the SpeculatedType needs to be fed back // into the information gleaned from the RegisteredStructureSet. m_structure.filter(m_type); filterArrayModesByType(); filterValueByType(); return normalizeClarity(graph); } FiltrationResult AbstractValue::changeStructure(Graph& graph, const RegisteredStructureSet& other) { m_type &= other.speculationFromStructures(); m_arrayModes = other.arrayModesFromStructures(); m_structure = other; filterValueByType(); return normalizeClarity(graph); } FiltrationResult AbstractValue::filterArrayModes(ArrayModes arrayModes, SpeculatedType admittedTypes) { ASSERT(arrayModes); ASSERT(!(admittedTypes & SpecCell)); if (isClear()) return FiltrationOK; m_type &= SpecCell | admittedTypes; m_arrayModes &= arrayModes; return normalizeClarity(); } FiltrationResult AbstractValue::filterClassInfo(Graph& graph, const ClassInfo* classInfo) { // FIXME: AI should track ClassInfo to leverage hierarchical class information. // https://bugs.webkit.org/show_bug.cgi?id=162989 if (isClear()) return FiltrationOK; m_type &= speculationFromClassInfoInheritance(classInfo); m_structure.filterClassInfo(classInfo); m_structure.filter(m_type); filterArrayModesByType(); filterValueByType(); return normalizeClarity(graph); } FiltrationResult AbstractValue::filterSlow(SpeculatedType type) { m_type &= type; // It's possible that prior to this filter() call we had, say, (Final, TOP), and // the passed type is Array. At this point we'll have (None, TOP). The best way // to ensure that the structure filtering does the right thing is to filter on // the new type (None) rather than the one passed (Array). m_structure.filter(m_type); filterArrayModesByType(); filterValueByType(); return normalizeClarity(); } FiltrationResult AbstractValue::fastForwardToAndFilterSlow(AbstractValueClobberEpoch newEpoch, SpeculatedType type) { if (newEpoch != m_effectEpoch) fastForwardToSlow(newEpoch); return filterSlow(type); } FiltrationResult AbstractValue::filterByValue(const FrozenValue& value) { FiltrationResult result = filter(speculationFromValue(value.value())); if (m_type) { m_value = value.value(); // It is possible that SpeculatedType from value is broader than original m_type. // The filter operation can only keep m_type as is or make it narrower. // As a result, the SpeculatedType from m_value can become broader than m_type. This breaks an invariant. // When setting m_value after filtering, we should filter m_value with m_type. filterValueByType(); } checkConsistency(); return result; } bool AbstractValue::contains(RegisteredStructure structure) const { return couldBeType(speculationFromStructure(structure.get())) && (m_arrayModes & arrayModesFromStructure(structure.get())) && m_structure.contains(structure); } FiltrationResult AbstractValue::filter(const AbstractValue& other) { m_type &= other.m_type; m_structure.filter(other.m_structure); m_arrayModes &= other.m_arrayModes; m_structure.filter(m_type); filterArrayModesByType(); filterValueByType(); if (normalizeClarity() == Contradiction) return Contradiction; if (m_value == other.m_value) return FiltrationOK; // Neither of us are BOTTOM, so an empty value means TOP. if (!m_value) { // We previously didn't prove a value but now we have done so. m_value = other.m_value; // It is possible that SpeculatedType from other.m_value is broader than original m_type. // The filter operation can only keep m_type as is or make it narrower. // As a result, the SpeculatedType from m_value can become broader than m_type. This breaks an invariant. // When setting m_value after filtering, we should filter m_value with m_type. filterValueByType(); return FiltrationOK; } if (!other.m_value) { // We had proved a value but the other guy hadn't, so keep our proof. return FiltrationOK; } // We both proved there to be a specific value but they are different. clear(); return Contradiction; } void AbstractValue::filterValueByType() { // We could go further, and ensure that if the futurePossibleStructure contravenes // the value, then we could clear both of those things. But that's unlikely to help // in any realistic scenario, so we don't do it. Simpler is better. if (!m_value) return; if (validateTypeAcceptingBoxedInt52(m_value)) return; // We assume that the constant value can produce a narrower type at // some point. For example, rope JSString produces SpecString, but // it produces SpecStringIdent once it is resolved to AtomStringImpl. // We do not make this AbstractValue cleared, but clear the constant // value if validation fails currently. m_value = JSValue(); } void AbstractValue::filterArrayModesByType() { if (!(m_type & SpecCell)) m_arrayModes = 0; else if (!(m_type & ~SpecArray)) m_arrayModes &= ALL_ARRAY_ARRAY_MODES; // NOTE: If m_type doesn't have SpecArray set, that doesn't mean that the // array modes have to be a subset of ALL_NON_ARRAY_ARRAY_MODES, since // in the speculated type type-system, RegExpMatchesArry and ArrayPrototype // are Otherobj (since they are not *exactly* JSArray) but in the ArrayModes // type system they are arrays (since they expose the magical length // property and are otherwise allocated using array allocation). Hence the // following would be wrong: // // if (!(m_type & SpecArray)) // m_arrayModes &= ALL_NON_ARRAY_ARRAY_MODES; } bool AbstractValue::shouldBeClear() const { if (m_type == SpecNone) return true; if (!(m_type & ~SpecCell) && (!m_arrayModes || m_structure.isClear())) return true; return false; } FiltrationResult AbstractValue::normalizeClarity() { // It's useful to be able to quickly check if an abstract value is clear. // This normalizes everything to make that easy. FiltrationResult result; if (shouldBeClear()) { clear(); result = Contradiction; } else result = FiltrationOK; checkConsistency(); return result; } FiltrationResult AbstractValue::normalizeClarity(Graph& graph) { FiltrationResult result = normalizeClarity(); assertIsRegistered(graph); return result; } #if ASSERT_ENABLED void AbstractValue::checkConsistency() const { if (!(m_type & SpecCell)) { RELEASE_ASSERT(m_structure.isClear()); RELEASE_ASSERT(!m_arrayModes); } if (isClear()) RELEASE_ASSERT(!m_value); if (!!m_value) RELEASE_ASSERT(validateTypeAcceptingBoxedInt52(m_value)); // Note that it's possible for a prediction like (Final, []). This really means that // the value is bottom and that any code that uses the value is unreachable. But // we don't want to get pedantic about this as it would only increase the computational // complexity of the code. } void AbstractValue::assertIsRegistered(Graph& graph) const { m_structure.assertIsRegistered(graph); } #endif // ASSERT_ENABLED ResultType AbstractValue::resultType() const { ASSERT(isType(SpecBytecodeTop)); if (isType(SpecBoolean)) return ResultType::booleanType(); if (isType(SpecInt32Only)) return ResultType::numberTypeIsInt32(); if (isType(SpecBytecodeNumber)) return ResultType::numberType(); if (isType(SpecString)) return ResultType::stringType(); if (isType(SpecString | SpecBytecodeNumber)) return ResultType::stringOrNumberType(); return ResultType::unknownType(); } void AbstractValue::dump(PrintStream& out) const { dumpInContext(out, nullptr); } void AbstractValue::dumpInContext(PrintStream& out, DumpContext* context) const { out.print("(", SpeculationDump(m_type)); if (m_type & SpecCell) { out.print( ", ", ArrayModesDump(m_arrayModes), ", ", inContext(m_structure, context)); } if (!!m_value) out.print(", ", inContext(m_value, context)); out.print(", ", m_effectEpoch); out.print(")"); } void AbstractValue::validateReferences(const TrackedReferences& trackedReferences) { trackedReferences.check(m_value); m_structure.validateReferences(trackedReferences); } #if USE(JSVALUE64) && !defined(NDEBUG) void AbstractValue::ensureCanInitializeWithZeros() { std::aligned_storage::type zeroFilledStorage; memset(static_cast(&zeroFilledStorage), 0, sizeof(AbstractValue)); ASSERT(*this == *static_cast(static_cast(&zeroFilledStorage))); } #endif void AbstractValue::fastForwardToSlow(AbstractValueClobberEpoch newEpoch) { ASSERT(newEpoch != m_effectEpoch); if (newEpoch.clobberEpoch() != m_effectEpoch.clobberEpoch()) clobberStructures(); if (newEpoch.structureClobberState() == StructuresAreWatched) m_structure.observeInvalidationPoint(); m_effectEpoch = newEpoch; checkConsistency(); } } } // namespace JSC::DFG #endif // ENABLE(DFG_JIT)