/* * Copyright (C) 2012 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "DFGArrayMode.h" #if ENABLE(DFG_JIT) #include "DFGAbstractValue.h" #include "DFGGraph.h" #include "Operations.h" namespace JSC { namespace DFG { ArrayMode ArrayMode::fromObserved(ArrayProfile* profile, Array::Action action, bool makeSafe) { ArrayModes observed = profile->observedArrayModes(); switch (observed) { case 0: return ArrayMode(Array::Unprofiled); case asArrayModes(NonArray): if (action == Array::Write && !profile->mayInterceptIndexedAccesses()) return ArrayMode(Array::Undecided, Array::NonArray, Array::OutOfBounds, Array::Convert); return ArrayMode(Array::SelectUsingPredictions); case asArrayModes(ArrayWithUndecided): if (action == Array::Write) return ArrayMode(Array::Undecided, Array::Array, Array::OutOfBounds, Array::Convert); return ArrayMode(Array::Generic); case asArrayModes(NonArray) | asArrayModes(ArrayWithUndecided): if (action == Array::Write && !profile->mayInterceptIndexedAccesses()) return ArrayMode(Array::Undecided, Array::PossiblyArray, Array::OutOfBounds, Array::Convert); return ArrayMode(Array::SelectUsingPredictions); case asArrayModes(NonArrayWithInt32): return ArrayMode(Array::Int32, Array::NonArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(ArrayWithInt32): return ArrayMode(Array::Int32, Array::Array, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithInt32) | asArrayModes(ArrayWithInt32): return ArrayMode(Array::Int32, Array::PossiblyArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithDouble): return ArrayMode(Array::Double, Array::NonArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(ArrayWithDouble): return ArrayMode(Array::Double, Array::Array, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithDouble) | asArrayModes(ArrayWithDouble): return ArrayMode(Array::Double, Array::PossiblyArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithContiguous): return ArrayMode(Array::Contiguous, Array::NonArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(ArrayWithContiguous): return ArrayMode(Array::Contiguous, Array::Array, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithContiguous) | asArrayModes(ArrayWithContiguous): return ArrayMode(Array::Contiguous, Array::PossiblyArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithArrayStorage): return ArrayMode(Array::ArrayStorage, Array::NonArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithSlowPutArrayStorage): case asArrayModes(NonArrayWithArrayStorage) | asArrayModes(NonArrayWithSlowPutArrayStorage): return ArrayMode(Array::SlowPutArrayStorage, Array::NonArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(ArrayWithArrayStorage): return ArrayMode(Array::ArrayStorage, Array::Array, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(ArrayWithSlowPutArrayStorage): case asArrayModes(ArrayWithArrayStorage) | asArrayModes(ArrayWithSlowPutArrayStorage): return ArrayMode(Array::SlowPutArrayStorage, Array::Array, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithArrayStorage) | asArrayModes(ArrayWithArrayStorage): return ArrayMode(Array::ArrayStorage, Array::PossiblyArray, Array::AsIs).withProfile(profile, makeSafe); case asArrayModes(NonArrayWithSlowPutArrayStorage) | asArrayModes(ArrayWithSlowPutArrayStorage): case asArrayModes(NonArrayWithArrayStorage) | asArrayModes(ArrayWithArrayStorage) | asArrayModes(NonArrayWithSlowPutArrayStorage) | asArrayModes(ArrayWithSlowPutArrayStorage): return ArrayMode(Array::SlowPutArrayStorage, Array::PossiblyArray, Array::AsIs).withProfile(profile, makeSafe); default: if ((observed & asArrayModes(NonArray)) && profile->mayInterceptIndexedAccesses()) return ArrayMode(Array::SelectUsingPredictions); Array::Type type; Array::Class arrayClass; if (shouldUseSlowPutArrayStorage(observed)) type = Array::SlowPutArrayStorage; else if (shouldUseFastArrayStorage(observed)) type = Array::ArrayStorage; else if (shouldUseContiguous(observed)) type = Array::Contiguous; else if (shouldUseDouble(observed)) type = Array::Double; else if (shouldUseInt32(observed)) type = Array::Int32; else type = Array::Undecided; if (hasSeenArray(observed) && hasSeenNonArray(observed)) arrayClass = Array::PossiblyArray; else if (hasSeenArray(observed)) arrayClass = Array::Array; else if (hasSeenNonArray(observed)) arrayClass = Array::NonArray; else arrayClass = Array::PossiblyArray; return ArrayMode(type, arrayClass, Array::Convert).withProfile(profile, makeSafe); } } ArrayMode ArrayMode::refine(SpeculatedType base, SpeculatedType index, SpeculatedType value, NodeFlags flags) const { if (!base || !index) { // It can be that we had a legitimate arrayMode but no incoming predictions. That'll // happen if we inlined code based on, say, a global variable watchpoint, but later // realized that the callsite could not have possibly executed. It may be worthwhile // to fix that, but for now I'm leaving it as-is. return ArrayMode(Array::ForceExit); } if (!isInt32Speculation(index)) return ArrayMode(Array::Generic); // Note: our profiling currently doesn't give us good information in case we have // an unlikely control flow path that sets the base to a non-cell value. Value // profiling and prediction propagation will probably tell us that the value is // either a cell or not, but that doesn't tell us which is more likely: that this // is an array access on a cell (what we want and can optimize) or that the user is // doing a crazy by-val access on a primitive (we can't easily optimize this and // don't want to). So, for now, we assume that if the base is not a cell according // to value profiling, but the array profile tells us something else, then we // should just trust the array profile. switch (type()) { case Array::Unprofiled: return ArrayMode(Array::ForceExit); case Array::Undecided: if (!value) return withType(Array::ForceExit); if (isInt32Speculation(value)) return withTypeAndConversion(Array::Int32, Array::Convert); if (isNumberSpeculation(value)) return withTypeAndConversion(Array::Double, Array::Convert); return withTypeAndConversion(Array::Contiguous, Array::Convert); case Array::Int32: if (!value || isInt32Speculation(value)) return *this; if (isNumberSpeculation(value)) return withTypeAndConversion(Array::Double, Array::Convert); return withTypeAndConversion(Array::Contiguous, Array::Convert); case Array::Double: if (flags & NodeUsedAsInt) return withTypeAndConversion(Array::Contiguous, Array::RageConvert); if (!value || isNumberSpeculation(value)) return *this; return withTypeAndConversion(Array::Contiguous, Array::Convert); case Array::Contiguous: if (doesConversion() && (flags & NodeUsedAsInt)) return withConversion(Array::RageConvert); return *this; case Array::SelectUsingPredictions: base &= ~SpecOther; if (isStringSpeculation(base)) return ArrayMode(Array::String); if (isArgumentsSpeculation(base)) return ArrayMode(Array::Arguments); if (isInt8ArraySpeculation(base)) return ArrayMode(Array::Int8Array); if (isInt16ArraySpeculation(base)) return ArrayMode(Array::Int16Array); if (isInt32ArraySpeculation(base)) return ArrayMode(Array::Int32Array); if (isUint8ArraySpeculation(base)) return ArrayMode(Array::Uint8Array); if (isUint8ClampedArraySpeculation(base)) return ArrayMode(Array::Uint8ClampedArray); if (isUint16ArraySpeculation(base)) return ArrayMode(Array::Uint16Array); if (isUint32ArraySpeculation(base)) return ArrayMode(Array::Uint32Array); if (isFloat32ArraySpeculation(base)) return ArrayMode(Array::Float32Array); if (isFloat64ArraySpeculation(base)) return ArrayMode(Array::Float64Array); return ArrayMode(Array::Generic); default: return *this; } } Structure* ArrayMode::originalArrayStructure(Graph& graph, const CodeOrigin& codeOrigin) const { if (!isJSArrayWithOriginalStructure()) return 0; JSGlobalObject* globalObject = graph.globalObjectFor(codeOrigin); switch (type()) { case Array::Int32: return globalObject->originalArrayStructureForIndexingType(ArrayWithInt32); case Array::Double: return globalObject->originalArrayStructureForIndexingType(ArrayWithDouble); case Array::Contiguous: return globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous); case Array::ArrayStorage: return globalObject->originalArrayStructureForIndexingType(ArrayWithArrayStorage); default: CRASH(); return 0; } } Structure* ArrayMode::originalArrayStructure(Graph& graph, Node* node) const { return originalArrayStructure(graph, node->codeOrigin); } bool ArrayMode::alreadyChecked(Graph& graph, Node* node, AbstractValue& value, IndexingType shape) const { switch (arrayClass()) { case Array::OriginalArray: return value.m_currentKnownStructure.hasSingleton() && (value.m_currentKnownStructure.singleton()->indexingType() & IndexingShapeMask) == shape && (value.m_currentKnownStructure.singleton()->indexingType() & IsArray) && graph.globalObjectFor(node->codeOrigin)->isOriginalArrayStructure(value.m_currentKnownStructure.singleton()); case Array::Array: if (arrayModesAlreadyChecked(value.m_arrayModes, asArrayModes(shape | IsArray))) return true; return value.m_currentKnownStructure.hasSingleton() && (value.m_currentKnownStructure.singleton()->indexingType() & IndexingShapeMask) == shape && (value.m_currentKnownStructure.singleton()->indexingType() & IsArray); default: if (arrayModesAlreadyChecked(value.m_arrayModes, asArrayModes(shape) | asArrayModes(shape | IsArray))) return true; return value.m_currentKnownStructure.hasSingleton() && (value.m_currentKnownStructure.singleton()->indexingType() & IndexingShapeMask) == shape; } } bool ArrayMode::alreadyChecked(Graph& graph, Node* node, AbstractValue& value) const { switch (type()) { case Array::Generic: return true; case Array::ForceExit: return false; case Array::String: return speculationChecked(value.m_type, SpecString); case Array::Int32: return alreadyChecked(graph, node, value, Int32Shape); case Array::Double: return alreadyChecked(graph, node, value, DoubleShape); case Array::Contiguous: return alreadyChecked(graph, node, value, ContiguousShape); case Array::ArrayStorage: return alreadyChecked(graph, node, value, ArrayStorageShape); case Array::SlowPutArrayStorage: switch (arrayClass()) { case Array::OriginalArray: CRASH(); return false; case Array::Array: if (arrayModesAlreadyChecked(value.m_arrayModes, asArrayModes(ArrayWithArrayStorage) | asArrayModes(ArrayWithSlowPutArrayStorage))) return true; return value.m_currentKnownStructure.hasSingleton() && hasArrayStorage(value.m_currentKnownStructure.singleton()->indexingType()) && (value.m_currentKnownStructure.singleton()->indexingType() & IsArray); default: if (arrayModesAlreadyChecked(value.m_arrayModes, asArrayModes(NonArrayWithArrayStorage) | asArrayModes(ArrayWithArrayStorage) | asArrayModes(NonArrayWithSlowPutArrayStorage) | asArrayModes(ArrayWithSlowPutArrayStorage))) return true; return value.m_currentKnownStructure.hasSingleton() && hasArrayStorage(value.m_currentKnownStructure.singleton()->indexingType()); } case Array::Arguments: return speculationChecked(value.m_type, SpecArguments); case Array::Int8Array: return speculationChecked(value.m_type, SpecInt8Array); case Array::Int16Array: return speculationChecked(value.m_type, SpecInt16Array); case Array::Int32Array: return speculationChecked(value.m_type, SpecInt32Array); case Array::Uint8Array: return speculationChecked(value.m_type, SpecUint8Array); case Array::Uint8ClampedArray: return speculationChecked(value.m_type, SpecUint8ClampedArray); case Array::Uint16Array: return speculationChecked(value.m_type, SpecUint16Array); case Array::Uint32Array: return speculationChecked(value.m_type, SpecUint32Array); case Array::Float32Array: return speculationChecked(value.m_type, SpecFloat32Array); case Array::Float64Array: return speculationChecked(value.m_type, SpecFloat64Array); case Array::SelectUsingPredictions: case Array::Unprofiled: case Array::Undecided: break; } CRASH(); return false; } const char* arrayTypeToString(Array::Type type) { switch (type) { case Array::SelectUsingPredictions: return "SelectUsingPredictions"; case Array::Unprofiled: return "Unprofiled"; case Array::Generic: return "Generic"; case Array::ForceExit: return "ForceExit"; case Array::String: return "String"; case Array::Undecided: return "Undecided"; case Array::Int32: return "Int32"; case Array::Double: return "Double"; case Array::Contiguous: return "Contiguous"; case Array::ArrayStorage: return "ArrayStorage"; case Array::SlowPutArrayStorage: return "SlowPutArrayStorage"; case Array::Arguments: return "Arguments"; case Array::Int8Array: return "Int8Array"; case Array::Int16Array: return "Int16Array"; case Array::Int32Array: return "Int32Array"; case Array::Uint8Array: return "Uint8Array"; case Array::Uint8ClampedArray: return "Uint8ClampedArray"; case Array::Uint16Array: return "Uint16Array"; case Array::Uint32Array: return "Uint32Array"; case Array::Float32Array: return "Float32Array"; case Array::Float64Array: return "Float64Array"; default: // Better to return something then it is to crash. Remember, this method // is being called from our main diagnostic tool, the IR dumper. It's like // a stack trace. So if we get here then probably something has already // gone wrong. return "Unknown!"; } } const char* arrayClassToString(Array::Class arrayClass) { switch (arrayClass) { case Array::Array: return "Array"; case Array::OriginalArray: return "OriginalArray"; case Array::NonArray: return "NonArray"; case Array::PossiblyArray: return "PossiblyArray"; default: return "Unknown!"; } } const char* arraySpeculationToString(Array::Speculation speculation) { switch (speculation) { case Array::SaneChain: return "SaneChain"; case Array::InBounds: return "InBounds"; case Array::ToHole: return "ToHole"; case Array::OutOfBounds: return "OutOfBounds"; default: return "Unknown!"; } } const char* arrayConversionToString(Array::Conversion conversion) { switch (conversion) { case Array::AsIs: return "AsIs"; case Array::Convert: return "Convert"; case Array::RageConvert: return "RageConvert"; default: return "Unknown!"; } } void ArrayMode::dump(PrintStream& out) const { out.print(type(), arrayClass(), speculation(), conversion()); } } } // namespace JSC::DFG namespace WTF { void printInternal(PrintStream& out, JSC::DFG::Array::Type type) { out.print(JSC::DFG::arrayTypeToString(type)); } void printInternal(PrintStream& out, JSC::DFG::Array::Class arrayClass) { out.print(JSC::DFG::arrayClassToString(arrayClass)); } void printInternal(PrintStream& out, JSC::DFG::Array::Speculation speculation) { out.print(JSC::DFG::arraySpeculationToString(speculation)); } void printInternal(PrintStream& out, JSC::DFG::Array::Conversion conversion) { out.print(JSC::DFG::arrayConversionToString(conversion)); } } // namespace WTF #endif // ENABLE(DFG_JIT)