Merge r170564, r170571, r170604, r170628, r170672, r170680, r170724, r170728, r170729, r170819, r170821, r170836, r170855, r170860, r170890, r170907, r170929, r171052, r171106, r171152, r171153, r171214 from ftlopt.
Source/JavaScriptCore:
This part of the merge delivers roughly a 2% across-the-board performance
improvement, mostly due to immutable property inference and DFG-side GCSE. It also
almost completely resolves accessor performance issues; in the common case the DFG
will compile a getter/setter access into code that is just as efficient as a normal
property access.
Another major highlight of this part of the merge is the work to add a type profiler
to the inspector. This work is still on-going but this greatly increases coverage.
Note that this merge fixes a minor bug in the GetterSetter refactoring from
http://trac.webkit.org/changeset/170729 (https://bugs.webkit.org/show_bug.cgi?id=134518).
It also adds a new tests to tests/stress to cover that bug. That bug was previously only
covered by layout tests.
2014-07-17 Filip Pizlo <fpizlo@apple.com>
[ftlopt] DFG Flush(SetLocal) store elimination is overzealous for captured variables in the presence of nodes that have no effects but may throw (merge trunk r171190)
https://bugs.webkit.org/show_bug.cgi?id=135019
Reviewed by Oliver Hunt.
Behaviorally, this is just a merge of trunk r171190, except that the relevant functionality
has moved to StrengthReductionPhase and is written in a different style. Same algorithm,
different code.
* dfg/DFGNodeType.h:
* dfg/DFGStrengthReductionPhase.cpp:
(JSC::DFG::StrengthReductionPhase::handleNode):
* tests/stress/capture-escape-and-throw.js: Added.
(foo.f):
(foo):
* tests/stress/new-array-with-size-throw-exception-and-tear-off-arguments.js: Added.
(foo):
(bar):
2014-07-15 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Constant fold GetGetter and GetSetter if the GetterSetter is a constant
https://bugs.webkit.org/show_bug.cgi?id=134962
Reviewed by Oliver Hunt.
This removes yet another steady-state-throughput implication of using getters and setters:
if your accessor call is monomorphic then you'll just get a structure check, nothing more.
No more loads to get to the GetterSetter object or the accessor function object.
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* runtime/GetterSetter.h:
(JSC::GetterSetter::getterConcurrently):
(JSC::GetterSetter::setGetter):
(JSC::GetterSetter::setterConcurrently):
(JSC::GetterSetter::setSetter):
2014-07-15 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Identity replacement in CSE shouldn't create a Phantom over the Identity's children
https://bugs.webkit.org/show_bug.cgi?id=134893
Reviewed by Oliver Hunt.
Replace Identity with Check instead of Phantom. Phantom means that the child of the
Identity should be unconditionally live. The liveness semantics of Identity are such that
if the parents of Identity are live then the child is live. Removing the Identity entirely
preserves such liveness semantics. So, the only thing that should be left behind is the
type check on the child, which is what Check means: do the check but don't keep the child
alive if the check isn't needed.
* dfg/DFGCSEPhase.cpp:
* dfg/DFGNode.h:
(JSC::DFG::Node::convertToCheck):
2014-07-13 Filip Pizlo <fpizlo@apple.com>
[ftlopt] DFG should be able to do GCSE in SSA and this should be unified with the CSE in CPS, and both of these things should use abstract heaps for reasoning about effects
https://bugs.webkit.org/show_bug.cgi?id=134677
Reviewed by Sam Weinig.
This removes the old local CSE phase, which was based on manually written backward-search
rules for all of the different kinds of things we cared about, and adds a new local/global
CSE (local for CPS and global for SSA) that leaves the node semantics almost entirely up to
clobberize(). Thus, the CSE phase itself just worries about the algorithms and data
structures used for storing sets of available values. This results in a large reduction in
code size in CSEPhase.cpp while greatly increasing the phase's power (since it now does
global CSE) and reducing compile time (since local CSE is now rewritten to use smarter data
structures). Even though LLVM was already running GVN, the extra GCSE at DFG IR level means
that this is a significant (~0.7%) throughput improvement.
This work is based on the concept of "def" to clobberize(). If clobberize() calls def(), it
means that the node being analyzed makes available some value in some DFG node, and that
future attempts to compute that value can simply use that node. In other words, it
establishes an available value mapping of the form value=>node. There are two kinds of
values that can be passed to def():
PureValue. This captures everything needed to determine whether two pure nodes - nodes that
neither read nor write, and produce a value that is a CSE candidate - are identical. It
carries the NodeType, an AdjacencyList, and one word of meta-data. The meta-data is
usually used for things like the arithmetic mode or constant pointer. Passing a
PureValue to def() means that the node produces a value that is valid anywhere that the
node dominates.
HeapLocation. This describes a location in the heap that could be written to or read from.
Both stores and loads can def() a HeapLocation. HeapLocation carries around an abstract
heap that both serves as part of the "name" of the heap location (together with the
other fields of HeapLocation) and also tells us what write()'s to watch for. If someone
write()'s to an abstract heap that overlaps the heap associated with the HeapLocation,
then it means that the values for that location are no longer available.
This approach is sufficiently clever that the CSEPhase itself can focus on the mechanism of
tracking the PureValue=>node and HeapLocation=>node maps, without having to worry about
interpreting the semantics of different DFG node types - that is now almost entirely in
clobberize(). The only things we special-case inside CSEPhase are the Identity node, which
CSE is traditionally responsible for eliminating even though it has nothing to do with CSE,
and the LocalCSE rule for turning PutByVal into PutByValAlias.
This is a slight Octane, SunSpider, and Kraken speed-up - all somewhere arond 0.7% . It's
not a bigger win because LLVM was already giving us most of what we needed in its GVN.
Also, the SunSpider speed-up isn't from GCSE as much as it's a clean-up of local CSE - that
is no longer O(n^2). Basically this is purely good: it reduces the amount of LLVM IR we
generate, it removes the old CSE's heap modeling (which was a constant source of bugs), and
it improves both the quality of the code we generate and the speed with which we generate
it. Also, any future optimizations that depend on GCSE will now be easier to implement.
During the development of this patch I also rationalized some other stuff, like Graph's
ordered traversals - we now have preorder and postorder rather than just "depth first".
* CMakeLists.txt:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
* JavaScriptCore.xcodeproj/project.pbxproj:
* dfg/DFGAbstractHeap.h:
* dfg/DFGAdjacencyList.h:
(JSC::DFG::AdjacencyList::hash):
(JSC::DFG::AdjacencyList::operator==):
* dfg/DFGBasicBlock.h:
* dfg/DFGCSEPhase.cpp:
(JSC::DFG::performLocalCSE):
(JSC::DFG::performGlobalCSE):
(JSC::DFG::CSEPhase::CSEPhase): Deleted.
(JSC::DFG::CSEPhase::run): Deleted.
(JSC::DFG::CSEPhase::endIndexForPureCSE): Deleted.
(JSC::DFG::CSEPhase::pureCSE): Deleted.
(JSC::DFG::CSEPhase::constantCSE): Deleted.
(JSC::DFG::CSEPhase::constantStoragePointerCSE): Deleted.
(JSC::DFG::CSEPhase::getCalleeLoadElimination): Deleted.
(JSC::DFG::CSEPhase::getArrayLengthElimination): Deleted.
(JSC::DFG::CSEPhase::globalVarLoadElimination): Deleted.
(JSC::DFG::CSEPhase::scopedVarLoadElimination): Deleted.
(JSC::DFG::CSEPhase::varInjectionWatchpointElimination): Deleted.
(JSC::DFG::CSEPhase::getByValLoadElimination): Deleted.
(JSC::DFG::CSEPhase::checkFunctionElimination): Deleted.
(JSC::DFG::CSEPhase::checkExecutableElimination): Deleted.
(JSC::DFG::CSEPhase::checkStructureElimination): Deleted.
(JSC::DFG::CSEPhase::structureTransitionWatchpointElimination): Deleted.
(JSC::DFG::CSEPhase::getByOffsetLoadElimination): Deleted.
(JSC::DFG::CSEPhase::getGetterSetterByOffsetLoadElimination): Deleted.
(JSC::DFG::CSEPhase::getPropertyStorageLoadElimination): Deleted.
(JSC::DFG::CSEPhase::checkArrayElimination): Deleted.
(JSC::DFG::CSEPhase::getIndexedPropertyStorageLoadElimination): Deleted.
(JSC::DFG::CSEPhase::getInternalFieldLoadElimination): Deleted.
(JSC::DFG::CSEPhase::getMyScopeLoadElimination): Deleted.
(JSC::DFG::CSEPhase::getLocalLoadElimination): Deleted.
(JSC::DFG::CSEPhase::invalidationPointElimination): Deleted.
(JSC::DFG::CSEPhase::setReplacement): Deleted.
(JSC::DFG::CSEPhase::eliminate): Deleted.
(JSC::DFG::CSEPhase::performNodeCSE): Deleted.
(JSC::DFG::CSEPhase::performBlockCSE): Deleted.
(JSC::DFG::performCSE): Deleted.
* dfg/DFGCSEPhase.h:
* dfg/DFGClobberSet.cpp:
(JSC::DFG::addReads):
(JSC::DFG::addWrites):
(JSC::DFG::addReadsAndWrites):
(JSC::DFG::readsOverlap):
(JSC::DFG::writesOverlap):
* dfg/DFGClobberize.cpp:
(JSC::DFG::doesWrites):
(JSC::DFG::accessesOverlap):
(JSC::DFG::writesOverlap):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
(JSC::DFG::NoOpClobberize::operator()):
(JSC::DFG::CheckClobberize::operator()):
(JSC::DFG::ReadMethodClobberize::ReadMethodClobberize):
(JSC::DFG::ReadMethodClobberize::operator()):
(JSC::DFG::WriteMethodClobberize::WriteMethodClobberize):
(JSC::DFG::WriteMethodClobberize::operator()):
(JSC::DFG::DefMethodClobberize::DefMethodClobberize):
(JSC::DFG::DefMethodClobberize::operator()):
* dfg/DFGDCEPhase.cpp:
(JSC::DFG::DCEPhase::run):
(JSC::DFG::DCEPhase::fixupBlock):
* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::getBlocksInPreOrder):
(JSC::DFG::Graph::getBlocksInPostOrder):
(JSC::DFG::Graph::addForDepthFirstSort): Deleted.
(JSC::DFG::Graph::getBlocksInDepthFirstOrder): Deleted.
* dfg/DFGGraph.h:
* dfg/DFGHeapLocation.cpp: Added.
(JSC::DFG::HeapLocation::dump):
(WTF::printInternal):
* dfg/DFGHeapLocation.h: Added.
(JSC::DFG::HeapLocation::HeapLocation):
(JSC::DFG::HeapLocation::operator!):
(JSC::DFG::HeapLocation::kind):
(JSC::DFG::HeapLocation::heap):
(JSC::DFG::HeapLocation::base):
(JSC::DFG::HeapLocation::index):
(JSC::DFG::HeapLocation::hash):
(JSC::DFG::HeapLocation::operator==):
(JSC::DFG::HeapLocation::isHashTableDeletedValue):
(JSC::DFG::HeapLocationHash::hash):
(JSC::DFG::HeapLocationHash::equal):
* dfg/DFGLICMPhase.cpp:
(JSC::DFG::LICMPhase::run):
* dfg/DFGNode.h:
(JSC::DFG::Node::replaceWith):
(JSC::DFG::Node::convertToPhantomUnchecked): Deleted.
* dfg/DFGPlan.cpp:
(JSC::DFG::Plan::compileInThreadImpl):
* dfg/DFGPureValue.cpp: Added.
(JSC::DFG::PureValue::dump):
* dfg/DFGPureValue.h: Added.
(JSC::DFG::PureValue::PureValue):
(JSC::DFG::PureValue::operator!):
(JSC::DFG::PureValue::op):
(JSC::DFG::PureValue::children):
(JSC::DFG::PureValue::info):
(JSC::DFG::PureValue::hash):
(JSC::DFG::PureValue::operator==):
(JSC::DFG::PureValue::isHashTableDeletedValue):
(JSC::DFG::PureValueHash::hash):
(JSC::DFG::PureValueHash::equal):
* dfg/DFGSSAConversionPhase.cpp:
(JSC::DFG::SSAConversionPhase::run):
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::LowerDFGToLLVM::lower):
2014-07-13 Filip Pizlo <fpizlo@apple.com>
Unreviewed, revert unintended change in r171051.
* dfg/DFGCSEPhase.cpp:
2014-07-08 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Move Flush(SetLocal) store elimination to StrengthReductionPhase
https://bugs.webkit.org/show_bug.cgi?id=134739
Reviewed by Mark Hahnenberg.
I'm going to streamline CSE around clobberize() as part of
https://bugs.webkit.org/show_bug.cgi?id=134677, and so Flush(SetLocal) store
elimination wouldn't belong in CSE anymore. It doesn't quite belong anywhere, which
means that it belongs in StrengthReductionPhase, since that's intended to be our
dumping ground.
To do this I had to add some missing smarts to clobberize(). Previously clobberize()
could play a bit loose with reads of Variables because it wasn't used for store
elimination. The main client of read() was LICM, but it would only use it to
determine hoistability and anything that did a write() was not hoistable - so, we had
benign (but still wrong) missing read() calls in places that did write()s. This fixes
a bunch of those cases.
* dfg/DFGCSEPhase.cpp:
(JSC::DFG::CSEPhase::performNodeCSE):
(JSC::DFG::CSEPhase::setLocalStoreElimination): Deleted.
* dfg/DFGClobberize.cpp:
(JSC::DFG::accessesOverlap):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize): Make clobberize() smart enough for detecting when this store elimination would be sound.
* dfg/DFGStrengthReductionPhase.cpp:
(JSC::DFG::StrengthReductionPhase::handleNode): Implement the store elimination in terms of clobberize().
2014-07-08 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Phantom simplification should be in its own phase
https://bugs.webkit.org/show_bug.cgi?id=134742
Reviewed by Geoffrey Garen.
This moves Phantom simplification out of CSE, which greatly simplifies CSE and gives it
more focus. Also this finally adds a phase that removes empty Phantoms. We sort of had
this in CPSRethreading, but that phase runs too infrequently and doesn't run at all for
SSA.
* CMakeLists.txt:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
* JavaScriptCore.xcodeproj/project.pbxproj:
* dfg/DFGAdjacencyList.h:
* dfg/DFGCSEPhase.cpp:
(JSC::DFG::CSEPhase::run):
(JSC::DFG::CSEPhase::setReplacement):
(JSC::DFG::CSEPhase::eliminate):
(JSC::DFG::CSEPhase::performNodeCSE):
(JSC::DFG::CSEPhase::eliminateIrrelevantPhantomChildren): Deleted.
* dfg/DFGPhantomRemovalPhase.cpp: Added.
(JSC::DFG::PhantomRemovalPhase::PhantomRemovalPhase):
(JSC::DFG::PhantomRemovalPhase::run):
(JSC::DFG::performCleanUp):
* dfg/DFGPhantomRemovalPhase.h: Added.
* dfg/DFGPlan.cpp:
(JSC::DFG::Plan::compileInThreadImpl):
2014-07-08 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Get rid of Node::misc by moving the fields out of the union so that you can use replacement and owner simultaneously
https://bugs.webkit.org/show_bug.cgi?id=134730
Reviewed by Mark Lam.
This will allow for a better GCSE implementation.
* dfg/DFGCPSRethreadingPhase.cpp:
(JSC::DFG::CPSRethreadingPhase::canonicalizeGetLocalFor):
* dfg/DFGCSEPhase.cpp:
(JSC::DFG::CSEPhase::setReplacement):
* dfg/DFGEdgeDominates.h:
(JSC::DFG::EdgeDominates::operator()):
* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::clearReplacements):
(JSC::DFG::Graph::initializeNodeOwners):
* dfg/DFGGraph.h:
(JSC::DFG::Graph::performSubstitutionForEdge):
* dfg/DFGLICMPhase.cpp:
(JSC::DFG::LICMPhase::attemptHoist):
* dfg/DFGNode.h:
(JSC::DFG::Node::Node):
* dfg/DFGSSAConversionPhase.cpp:
(JSC::DFG::SSAConversionPhase::run):
2014-07-04 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Infer immutable object properties
https://bugs.webkit.org/show_bug.cgi?id=134567
Reviewed by Mark Hahnenberg.
This introduces a new way of inferring immutable object properties. A property is said to
be immutable if after its creation (i.e. the transition that creates it), we never
overwrite it (i.e. replace it) or delete it. Immutability is a property of an "own
property" - so if we say that "f" is immutable at "o" then we are implying that "o" has "f"
directly and not on a prototype. More specifically, the immutability inference will prove
that a property on some structure is immutable. This means that, for example, we may have a
structure S1 with property "f" where we claim that "f" at S1 is immutable, but S1 has a
transition to S2 that adds a new property "g" and we may claim that "f" at S2 is actually
mutable. This is mainly for convenience; it allows us to decouple immutability logic from
transition logic. Immutability can be used to constant-fold accesses to objects at
DFG-time. The DFG needs to prove the following to constant-fold the access:
- The base of the access must be a constant object pointer. We prove that a property at a
structure is immutable, but that says nothing of its value; each actual instance of that
property may have a different value. So, a constant object pointer is needed to get an
actual constant instance of the immutable value.
- A check (or watchpoint) must have been emitted proving that the object has a structure
that allows loading the property in question.
- The replacement watchpoint set of the property in the structure that we've proven the
object to have is still valid and we add a watchpoint to it lazily. The replacement
watchpoint set is the key new mechanism that this change adds. It's possible that we have
proven that the object has one of many structures, in which case each of those structures
needs a valid replacement watchpoint set.
The replacement watchpoint set is created the first time that any access to the property is
cached. A put replace cache will create, and immediately invalidate, the watchpoint set. A
get cache will create the watchpoint set and make it start watching. Any non-cached put
access will invalidate the watchpoint set if one had been created; the underlying algorithm
ensures that checking for the existence of a replacement watchpoint set is very fast in the
common case. This algorithm ensures that no cached access needs to ever do any work to
invalidate, or check the validity of, any replacement watchpoint sets. It also has some
other nice properties:
- It's very robust in its definition of immutability. The strictest that it will ever be is
that for any instance of the object, the property must be written to only once,
specifically at the time that the property is created. But it's looser than this in
practice. For example, the property may be written to any number of times before we add
the final property that the object will have before anyone reads the property; this works
since for optimization purposes we only care if we detect immutability on the structure
that the object will have when it is most frequently read from, not any previous
structure that the object had. Also, we may write to the property any number of times
before anyone caches accesses to it.
- It is mostly orthogonal to structure transitions. No new structures need to be created to
track the immutability of a property. Hence, there is no risk from this feature causing
more polymorphism. This is different from the previous "specificValue" constant
inference, which did cause additional structures to be created and sometimes those
structures led to fake polymorphism. This feature does leverage existing transitions to
do some of the watchpointing: property deletions don't fire the replacement watchpoint
set because that would cause a new structure and so the mandatory structure check would
fail. Also, this feature is guaranteed to never kick in for uncacheable dictionaries
because those wouldn't allow for cacheable accesses - and it takes a cacheable access for
this feature to be enabled.
- No memory overhead is incurred except when accesses to the property are cached.
Dictionary properties will typically have no meta-data for immutability. The number of
replacement watchpoint sets we allocate is proportional to the number of inline caches in
the program, which is typically must smaller than the number of structures or even the
number of objects.
This inference is far more powerful than the previous "specificValue" inference, so this
change also removes all of that code. It's interesting that the amount of code that is
changed to remove that feature is almost as big as the amount of code added to support the
new inference - and that's if you include the new tests in the tally. Without new tests,
it appears that the new feature actually touches less code!
There is one corner case where the previous "specificValue" inference was more powerful.
You can imagine someone creating objects with functions as self properties on those
objects, such that each object instance had the same function pointers - essentially,
someone might be trying to create a vtable but failing at the whole "one vtable for many
instances" concept. The "specificValue" inference would do very well for such programs,
because a structure check would be sufficient to prove a constant value for all of the
function properties. This new inference will fail because it doesn't track the constant
values of constant properties; instead it detects the immutability of otherwise variable
properties (in the sense that each instance of the property may have a different value).
So, the new inference requires having a particular object instance to actually get the
constant value. I think it's OK to lose this antifeature. It took a lot of code to support
and was a constant source of grief in our transition logic, and there doesn't appear to be
any real evidence that programs benefited from that particular kind of inference since
usually it's the singleton prototype instance that has all of the functions.
This change is a speed-up on everything. date-format-xparb and both SunSpider/raytrace and
V8/raytrace seem to be the biggest winners among the macrobenchmarks; they see >5%
speed-ups. Many of our microbenchmarks see very large performance improvements, even 80% in
one case.
* bytecode/ComplexGetStatus.cpp:
(JSC::ComplexGetStatus::computeFor):
* bytecode/GetByIdStatus.cpp:
(JSC::GetByIdStatus::computeFromLLInt):
(JSC::GetByIdStatus::computeForStubInfo):
(JSC::GetByIdStatus::computeFor):
* bytecode/GetByIdVariant.cpp:
(JSC::GetByIdVariant::GetByIdVariant):
(JSC::GetByIdVariant::operator=):
(JSC::GetByIdVariant::attemptToMerge):
(JSC::GetByIdVariant::dumpInContext):
* bytecode/GetByIdVariant.h:
(JSC::GetByIdVariant::alternateBase):
(JSC::GetByIdVariant::specificValue): Deleted.
* bytecode/PutByIdStatus.cpp:
(JSC::PutByIdStatus::computeForStubInfo):
(JSC::PutByIdStatus::computeFor):
* bytecode/PutByIdVariant.cpp:
(JSC::PutByIdVariant::operator=):
(JSC::PutByIdVariant::setter):
(JSC::PutByIdVariant::dumpInContext):
* bytecode/PutByIdVariant.h:
(JSC::PutByIdVariant::specificValue): Deleted.
* bytecode/Watchpoint.cpp:
(JSC::WatchpointSet::fireAllSlow):
(JSC::WatchpointSet::fireAll): Deleted.
* bytecode/Watchpoint.h:
(JSC::WatchpointSet::fireAll):
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleGetByOffset):
(JSC::DFG::ByteCodeParser::handleGetById):
(JSC::DFG::ByteCodeParser::handlePutById):
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::emitGetByOffset):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::isStringPrototypeMethodSane):
(JSC::DFG::FixupPhase::canOptimizeStringObjectAccess):
* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::tryGetConstantProperty):
(JSC::DFG::Graph::visitChildren):
* dfg/DFGGraph.h:
* dfg/DFGWatchableStructureWatchingPhase.cpp:
(JSC::DFG::WatchableStructureWatchingPhase::run):
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::LowerDFGToLLVM::compileMultiGetByOffset):
* jit/JITOperations.cpp:
* jit/Repatch.cpp:
(JSC::repatchByIdSelfAccess):
(JSC::generateByIdStub):
(JSC::tryCacheGetByID):
(JSC::tryCachePutByID):
(JSC::tryBuildPutByIdList):
* llint/LLIntSlowPaths.cpp:
(JSC::LLInt::LLINT_SLOW_PATH_DECL):
(JSC::LLInt::putToScopeCommon):
* runtime/CommonSlowPaths.h:
(JSC::CommonSlowPaths::tryCachePutToScopeGlobal):
* runtime/IntendedStructureChain.cpp:
(JSC::IntendedStructureChain::mayInterceptStoreTo):
* runtime/JSCJSValue.cpp:
(JSC::JSValue::putToPrimitive):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::reset):
* runtime/JSObject.cpp:
(JSC::JSObject::put):
(JSC::JSObject::putDirectNonIndexAccessor):
(JSC::JSObject::deleteProperty):
(JSC::JSObject::defaultValue):
(JSC::getCallableObjectSlow): Deleted.
(JSC::JSObject::getPropertySpecificValue): Deleted.
* runtime/JSObject.h:
(JSC::JSObject::getDirect):
(JSC::JSObject::getDirectOffset):
(JSC::JSObject::inlineGetOwnPropertySlot):
(JSC::JSObject::putDirectInternal):
(JSC::JSObject::putOwnDataProperty):
(JSC::JSObject::putDirect):
(JSC::JSObject::putDirectWithoutTransition):
(JSC::getCallableObject): Deleted.
* runtime/JSScope.cpp:
(JSC::abstractAccess):
* runtime/PropertyMapHashTable.h:
(JSC::PropertyMapEntry::PropertyMapEntry):
(JSC::PropertyTable::copy):
* runtime/PropertyTable.cpp:
(JSC::PropertyTable::clone):
(JSC::PropertyTable::PropertyTable):
(JSC::PropertyTable::visitChildren): Deleted.
* runtime/Structure.cpp:
(JSC::Structure::Structure):
(JSC::Structure::materializePropertyMap):
(JSC::Structure::addPropertyTransitionToExistingStructureImpl):
(JSC::Structure::addPropertyTransitionToExistingStructure):
(JSC::Structure::addPropertyTransitionToExistingStructureConcurrently):
(JSC::Structure::addPropertyTransition):
(JSC::Structure::changePrototypeTransition):
(JSC::Structure::attributeChangeTransition):
(JSC::Structure::toDictionaryTransition):
(JSC::Structure::preventExtensionsTransition):
(JSC::Structure::takePropertyTableOrCloneIfPinned):
(JSC::Structure::nonPropertyTransition):
(JSC::Structure::addPropertyWithoutTransition):
(JSC::Structure::allocateRareData):
(JSC::Structure::ensurePropertyReplacementWatchpointSet):
(JSC::Structure::startWatchingPropertyForReplacements):
(JSC::Structure::didCachePropertyReplacement):
(JSC::Structure::startWatchingInternalProperties):
(JSC::Structure::copyPropertyTable):
(JSC::Structure::copyPropertyTableForPinning):
(JSC::Structure::getConcurrently):
(JSC::Structure::get):
(JSC::Structure::add):
(JSC::Structure::visitChildren):
(JSC::Structure::prototypeChainMayInterceptStoreTo):
(JSC::Structure::dump):
(JSC::Structure::despecifyDictionaryFunction): Deleted.
(JSC::Structure::despecifyFunctionTransition): Deleted.
(JSC::Structure::despecifyFunction): Deleted.
(JSC::Structure::despecifyAllFunctions): Deleted.
(JSC::Structure::putSpecificValue): Deleted.
* runtime/Structure.h:
(JSC::Structure::startWatchingPropertyForReplacements):
(JSC::Structure::startWatchingInternalPropertiesIfNecessary):
(JSC::Structure::startWatchingInternalPropertiesIfNecessaryForEntireChain):
(JSC::Structure::transitionDidInvolveSpecificValue): Deleted.
(JSC::Structure::disableSpecificFunctionTracking): Deleted.
* runtime/StructureInlines.h:
(JSC::Structure::getConcurrently):
(JSC::Structure::didReplaceProperty):
(JSC::Structure::propertyReplacementWatchpointSet):
* runtime/StructureRareData.cpp:
(JSC::StructureRareData::destroy):
* runtime/StructureRareData.h:
* tests/stress/infer-constant-global-property.js: Added.
(foo.Math.sin):
(foo):
* tests/stress/infer-constant-property.js: Added.
(foo):
* tests/stress/jit-cache-poly-replace-then-cache-get-and-fold-then-invalidate.js: Added.
(foo):
(bar):
* tests/stress/jit-cache-replace-then-cache-get-and-fold-then-invalidate.js: Added.
(foo):
(bar):
* tests/stress/jit-put-to-scope-global-cache-watchpoint-invalidate.js: Added.
(foo):
(bar):
* tests/stress/llint-cache-replace-then-cache-get-and-fold-then-invalidate.js: Added.
(foo):
(bar):
* tests/stress/llint-put-to-scope-global-cache-watchpoint-invalidate.js: Added.
(foo):
(bar):
* tests/stress/repeat-put-to-scope-global-with-same-value-watchpoint-invalidate.js: Added.
(foo):
(bar):
2014-07-03 Saam Barati <sbarati@apple.com>
Add more coverage for the profile_types_with_high_fidelity op code.
https://bugs.webkit.org/show_bug.cgi?id=134616
Reviewed by Filip Pizlo.
More operations are now being recorded by the profile_types_with_high_fidelity
opcode. Specifically: function parameters, function return values,
function 'this' value, get_by_id, get_by_value, resolve nodes, function return
values at the call site. Added more flags to the profile_types_with_high_fidelity
opcode so more focused tasks can take place when the instruction is
being linked in CodeBlock. Re-worked the type profiler to search
through character offset ranges when asked for the type of an expression
at a given offset. Removed redundant calls to Structure::toStructureShape
in HighFidelityLog and TypeSet by caching calls based on StructureID.
* bytecode/BytecodeList.json:
* bytecode/BytecodeUseDef.h:
(JSC::computeUsesForBytecodeOffset):
(JSC::computeDefsForBytecodeOffset):
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::CodeBlock):
(JSC::CodeBlock::finalizeUnconditionally):
(JSC::CodeBlock::scopeDependentProfile):
* bytecode/CodeBlock.h:
(JSC::CodeBlock::returnStatementTypeSet):
* bytecode/TypeLocation.h:
* bytecode/UnlinkedCodeBlock.cpp:
(JSC::UnlinkedCodeBlock::highFidelityTypeProfileExpressionInfoForBytecodeOffset):
(JSC::UnlinkedCodeBlock::addHighFidelityTypeProfileExpressionInfo):
* bytecode/UnlinkedCodeBlock.h:
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitMove):
(JSC::BytecodeGenerator::emitProfileTypesWithHighFidelity):
(JSC::BytecodeGenerator::emitGetFromScopeWithProfile):
(JSC::BytecodeGenerator::emitPutToScope):
(JSC::BytecodeGenerator::emitPutToScopeWithProfile):
(JSC::BytecodeGenerator::emitPutById):
(JSC::BytecodeGenerator::emitPutByVal):
* bytecompiler/BytecodeGenerator.h:
(JSC::BytecodeGenerator::emitHighFidelityTypeProfilingExpressionInfo):
* bytecompiler/NodesCodegen.cpp:
(JSC::ResolveNode::emitBytecode):
(JSC::BracketAccessorNode::emitBytecode):
(JSC::DotAccessorNode::emitBytecode):
(JSC::FunctionCallValueNode::emitBytecode):
(JSC::FunctionCallResolveNode::emitBytecode):
(JSC::FunctionCallBracketNode::emitBytecode):
(JSC::FunctionCallDotNode::emitBytecode):
(JSC::CallFunctionCallDotNode::emitBytecode):
(JSC::ApplyFunctionCallDotNode::emitBytecode):
(JSC::PostfixNode::emitResolve):
(JSC::PostfixNode::emitBracket):
(JSC::PostfixNode::emitDot):
(JSC::PrefixNode::emitResolve):
(JSC::PrefixNode::emitBracket):
(JSC::PrefixNode::emitDot):
(JSC::ReadModifyResolveNode::emitBytecode):
(JSC::AssignResolveNode::emitBytecode):
(JSC::AssignDotNode::emitBytecode):
(JSC::ReadModifyDotNode::emitBytecode):
(JSC::AssignBracketNode::emitBytecode):
(JSC::ReadModifyBracketNode::emitBytecode):
(JSC::ReturnNode::emitBytecode):
(JSC::FunctionBodyNode::emitBytecode):
* inspector/agents/InspectorRuntimeAgent.cpp:
(Inspector::InspectorRuntimeAgent::getRuntimeTypeForVariableAtOffset):
(Inspector::InspectorRuntimeAgent::getRuntimeTypeForVariableInTextRange): Deleted.
* inspector/agents/InspectorRuntimeAgent.h:
* inspector/protocol/Runtime.json:
* llint/LLIntSlowPaths.cpp:
(JSC::LLInt::getFromScopeCommon):
(JSC::LLInt::LLINT_SLOW_PATH_DECL):
* llint/LLIntSlowPaths.h:
* llint/LowLevelInterpreter.asm:
* runtime/HighFidelityLog.cpp:
(JSC::HighFidelityLog::processHighFidelityLog):
(JSC::HighFidelityLog::actuallyProcessLogThreadFunction):
(JSC::HighFidelityLog::recordTypeInformationForLocation): Deleted.
* runtime/HighFidelityLog.h:
(JSC::HighFidelityLog::recordTypeInformationForLocation):
* runtime/HighFidelityTypeProfiler.cpp:
(JSC::HighFidelityTypeProfiler::getTypesForVariableInAtOffset):
(JSC::HighFidelityTypeProfiler::getGlobalTypesForVariableAtOffset):
(JSC::HighFidelityTypeProfiler::getLocalTypesForVariableAtOffset):
(JSC::HighFidelityTypeProfiler::insertNewLocation):
(JSC::HighFidelityTypeProfiler::findLocation):
(JSC::HighFidelityTypeProfiler::getTypesForVariableInRange): Deleted.
(JSC::HighFidelityTypeProfiler::getGlobalTypesForVariableInRange): Deleted.
(JSC::HighFidelityTypeProfiler::getLocalTypesForVariableInRange): Deleted.
(JSC::HighFidelityTypeProfiler::getLocationBasedHash): Deleted.
* runtime/HighFidelityTypeProfiler.h:
(JSC::LocationKey::LocationKey): Deleted.
(JSC::LocationKey::hash): Deleted.
(JSC::LocationKey::operator==): Deleted.
* runtime/Structure.cpp:
(JSC::Structure::toStructureShape):
* runtime/Structure.h:
* runtime/TypeSet.cpp:
(JSC::TypeSet::TypeSet):
(JSC::TypeSet::addTypeForValue):
(JSC::TypeSet::seenTypes):
(JSC::TypeSet::removeDuplicatesInStructureHistory): Deleted.
* runtime/TypeSet.h:
(JSC::StructureShape::setConstructorName):
* runtime/VM.cpp:
(JSC::VM::getTypesForVariableAtOffset):
(JSC::VM::dumpHighFidelityProfilingTypes):
(JSC::VM::getTypesForVariableInRange): Deleted.
* runtime/VM.h:
2014-07-04 Filip Pizlo <fpizlo@apple.com>
[ftlopt][REGRESSION] debug tests fail because PutByIdDirect is now implemented in terms of In
https://bugs.webkit.org/show_bug.cgi?id=134642
Rubber stamped by Andreas Kling.
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::LowerDFGToLLVM::compileNode):
2014-07-01 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Allocate a new GetterSetter if we change the value of any of its entries other than when they were previously null, so that if we constant-infer an accessor slot then we immediately get the function constant for free
https://bugs.webkit.org/show_bug.cgi?id=134518
Reviewed by Mark Hahnenberg.
This has no real effect right now, particularly since almost all uses of
setSetter/setGetter were already allocating a branch new GetterSetter. But once we start
doing more aggressive constant property inference, this change will allow us to remove
all runtime checks from getter/setter calls.
* runtime/GetterSetter.cpp:
(JSC::GetterSetter::withGetter):
(JSC::GetterSetter::withSetter):
* runtime/GetterSetter.h:
(JSC::GetterSetter::setGetter):
(JSC::GetterSetter::setSetter):
* runtime/JSObject.cpp:
(JSC::JSObject::defineOwnNonIndexProperty):
2014-07-02 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Rename notifyTransitionFromThisStructure to didTransitionFromThisStructure
Rubber stamped by Mark Hahnenberg.
* runtime/Structure.cpp:
(JSC::Structure::Structure):
(JSC::Structure::nonPropertyTransition):
(JSC::Structure::didTransitionFromThisStructure):
(JSC::Structure::notifyTransitionFromThisStructure): Deleted.
* runtime/Structure.h:
2014-07-02 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Remove the functionality for cloning StructureRareData since we never do that anymore.
Rubber stamped by Mark Hahnenberg.
* runtime/Structure.cpp:
(JSC::Structure::Structure):
(JSC::Structure::cloneRareDataFrom): Deleted.
* runtime/Structure.h:
* runtime/StructureRareData.cpp:
(JSC::StructureRareData::clone): Deleted.
(JSC::StructureRareData::StructureRareData): Deleted.
* runtime/StructureRareData.h:
(JSC::StructureRareData::needsCloning): Deleted.
2014-07-01 Mark Lam <mark.lam@apple.com>
[ftlopt] DebuggerCallFrame::scope() should return a DebuggerScope.
<https://webkit.org/b/134420>
Reviewed by Geoffrey Garen.
Previously, DebuggerCallFrame::scope() returns a JSActivation (and relevant
peers) which the WebInspector will use to introspect CallFrame variables.
Instead, we should be returning a DebuggerScope as an abstraction layer that
provides the introspection functionality that the WebInspector needs. This
is the first step towards not forcing every frame to have a JSActivation
object just because the debugger is enabled.
1. Instantiate the debuggerScopeStructure as a member of the JSGlobalObject
instead of the VM. This allows JSObject::globalObject() to be able to
return the global object for the DebuggerScope.
2. On the DebuggerScope's life-cycle management:
The DebuggerCallFrame is designed to be "valid" only during a debugging session
(while the debugger is broken) through the use of a DebuggerCallFrameScope in
Debugger::pauseIfNeeded(). Once the debugger resumes from the break, the
DebuggerCallFrameScope destructs, and the DebuggerCallFrame will be invalidated.
We can't guarantee (from this code alone) that the Inspector code isn't still
holding a ref to the DebuggerCallFrame (though they shouldn't), but by contract,
the frame will be invalidated, and any attempt to query it will return null values.
This is pre-existing behavior.
Now, we're adding the DebuggerScope into the picture. While a single debugger
pause session is in progress, the Inspector may request the scope from the
DebuggerCallFrame. While the DebuggerCallFrame is still valid, we want
DebuggerCallFrame::scope() to always return the same DebuggerScope object.
This is why we hold on to the DebuggerScope with a strong ref.
If we use a weak ref instead, the following cooky behavior can manifest:
1. The Inspector calls Debugger::scope() to get the top scope.
2. The Inspector iterates down the scope chain and is now only holding a
reference to a parent scope. It is no longer referencing the top scope.
3. A GC occurs, and the DebuggerCallFrame's weak m_scope ref to the top scope
gets cleared.
4. The Inspector calls DebuggerCallFrame::scope() to get the top scope again but gets
a different DebuggerScope instance.
5. The Inspector iterates down the scope chain but never sees the parent scope
instance that retained a ref to in step 2 above. This is because when iterating
this new DebuggerScope instance (which has no knowledge of the previous parent
DebuggerScope instance), a new DebuggerScope instance will get created for the
same parent scope.
Since the DebuggerScope is a JSObject, it's liveness is determined by its reachability.
However, it's "validity" is determined by the life-cycle of its owner DebuggerCallFrame.
When the owner DebuggerCallFrame gets invalidated, its debugger scope chain (if
instantiated) will also get invalidated. This is why we need the
DebuggerScope::invalidateChain() method. The Inspector should not be using the
DebuggerScope instance after its owner DebuggerCallFrame is invalidated. If it does,
those methods will do nothing or returned a failed status.
* debugger/Debugger.h:
* debugger/DebuggerCallFrame.cpp:
(JSC::DebuggerCallFrame::scope):
(JSC::DebuggerCallFrame::evaluate):
(JSC::DebuggerCallFrame::invalidate):
(JSC::DebuggerCallFrame::vm):
(JSC::DebuggerCallFrame::lexicalGlobalObject):
* debugger/DebuggerCallFrame.h:
* debugger/DebuggerScope.cpp:
(JSC::DebuggerScope::DebuggerScope):
(JSC::DebuggerScope::finishCreation):
(JSC::DebuggerScope::visitChildren):
(JSC::DebuggerScope::className):
(JSC::DebuggerScope::getOwnPropertySlot):
(JSC::DebuggerScope::put):
(JSC::DebuggerScope::deleteProperty):
(JSC::DebuggerScope::getOwnPropertyNames):
(JSC::DebuggerScope::defineOwnProperty):
(JSC::DebuggerScope::next):
(JSC::DebuggerScope::invalidateChain):
(JSC::DebuggerScope::isWithScope):
(JSC::DebuggerScope::isGlobalScope):
(JSC::DebuggerScope::isFunctionScope):
* debugger/DebuggerScope.h:
(JSC::DebuggerScope::create):
(JSC::DebuggerScope::Iterator::Iterator):
(JSC::DebuggerScope::Iterator::get):
(JSC::DebuggerScope::Iterator::operator++):
(JSC::DebuggerScope::Iterator::operator==):
(JSC::DebuggerScope::Iterator::operator!=):
(JSC::DebuggerScope::isValid):
(JSC::DebuggerScope::jsScope):
(JSC::DebuggerScope::begin):
(JSC::DebuggerScope::end):
* inspector/JSJavaScriptCallFrame.cpp:
(Inspector::JSJavaScriptCallFrame::scopeType):
(Inspector::JSJavaScriptCallFrame::scopeChain):
* inspector/JavaScriptCallFrame.h:
(Inspector::JavaScriptCallFrame::scopeChain):
* inspector/ScriptDebugServer.cpp:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::reset):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::debuggerScopeStructure):
* runtime/JSObject.h:
(JSC::JSObject::isWithScope):
* runtime/JSScope.h:
* runtime/VM.cpp:
(JSC::VM::VM):
* runtime/VM.h:
2014-07-01 Filip Pizlo <fpizlo@apple.com>
[ftlopt] DFG bytecode parser should turn PutById with nothing but a Setter stub as stuff+handleCall, and handleCall should be allowed to inline if it wants to
https://bugs.webkit.org/show_bug.cgi?id=130756
Reviewed by Oliver Hunt.
The enables exposing the call to setters in the DFG, and then inlining it. Previously we
already supproted inlined-cached calls to setters from within put_by_id inline caches,
and the DFG could certainly emit such IC's. Now, if an IC had a setter call, then the DFG
will either emit the GetGetterSetterByOffset/GetSetter/Call combo, or it will do one
better and inline the call.
A lot of the core functionality was already available from the previous work to inline
getters. So, there are some refactorings in this patch that move preexisting
functionality around. For example, the work to figure out how the DFG should go about
getting to what we call the "loaded value" - i.e. the GetterSetter object reference in
the case of accessors - is now shared in ComplexGetStatus, and both GetByIdStatus and
PutByIdStatus use it. This means that we can keep the safety checks common. This patch
also does additional refactorings in DFG::ByteCodeParser so that we can continue to reuse
handleCall() for all of the various kinds of calls we can now emit.
83% speed-up on getter-richards, 2% speed-up on box2d.
* CMakeLists.txt:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
* JavaScriptCore.xcodeproj/project.pbxproj:
* bytecode/ComplexGetStatus.cpp: Added.
(JSC::ComplexGetStatus::computeFor):
* bytecode/ComplexGetStatus.h: Added.
(JSC::ComplexGetStatus::ComplexGetStatus):
(JSC::ComplexGetStatus::skip):
(JSC::ComplexGetStatus::takesSlowPath):
(JSC::ComplexGetStatus::kind):
(JSC::ComplexGetStatus::attributes):
(JSC::ComplexGetStatus::specificValue):
(JSC::ComplexGetStatus::offset):
(JSC::ComplexGetStatus::chain):
* bytecode/GetByIdStatus.cpp:
(JSC::GetByIdStatus::computeForStubInfo):
* bytecode/GetByIdVariant.cpp:
(JSC::GetByIdVariant::GetByIdVariant):
* bytecode/PolymorphicPutByIdList.h:
(JSC::PutByIdAccess::PutByIdAccess):
(JSC::PutByIdAccess::setter):
(JSC::PutByIdAccess::structure):
(JSC::PutByIdAccess::chainCount):
* bytecode/PutByIdStatus.cpp:
(JSC::PutByIdStatus::computeFromLLInt):
(JSC::PutByIdStatus::computeFor):
(JSC::PutByIdStatus::computeForStubInfo):
(JSC::PutByIdStatus::makesCalls):
* bytecode/PutByIdStatus.h:
(JSC::PutByIdStatus::makesCalls): Deleted.
* bytecode/PutByIdVariant.cpp:
(JSC::PutByIdVariant::PutByIdVariant):
(JSC::PutByIdVariant::operator=):
(JSC::PutByIdVariant::replace):
(JSC::PutByIdVariant::transition):
(JSC::PutByIdVariant::setter):
(JSC::PutByIdVariant::writesStructures):
(JSC::PutByIdVariant::reallocatesStorage):
(JSC::PutByIdVariant::makesCalls):
(JSC::PutByIdVariant::dumpInContext):
* bytecode/PutByIdVariant.h:
(JSC::PutByIdVariant::PutByIdVariant):
(JSC::PutByIdVariant::structure):
(JSC::PutByIdVariant::oldStructure):
(JSC::PutByIdVariant::alternateBase):
(JSC::PutByIdVariant::specificValue):
(JSC::PutByIdVariant::callLinkStatus):
(JSC::PutByIdVariant::replace): Deleted.
(JSC::PutByIdVariant::transition): Deleted.
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::addCallWithoutSettingResult):
(JSC::DFG::ByteCodeParser::addCall):
(JSC::DFG::ByteCodeParser::handleCall):
(JSC::DFG::ByteCodeParser::handleInlining):
(JSC::DFG::ByteCodeParser::handleGetById):
(JSC::DFG::ByteCodeParser::handlePutById):
(JSC::DFG::ByteCodeParser::parseBlock):
* jit/Repatch.cpp:
(JSC::tryCachePutByID):
(JSC::tryBuildPutByIdList):
* runtime/IntendedStructureChain.cpp:
(JSC::IntendedStructureChain::takesSlowPathInDFGForImpureProperty):
* runtime/IntendedStructureChain.h:
* tests/stress/exit-from-setter.js: Added.
* tests/stress/poly-chain-setter.js: Added.
(Cons):
(foo):
(test):
* tests/stress/poly-chain-then-setter.js: Added.
(Cons1):
(Cons2):
(foo):
(test):
* tests/stress/poly-setter-combo.js: Added.
(Cons1):
(Cons2):
(foo):
(test):
(.test):
* tests/stress/poly-setter-then-self.js: Added.
(foo):
(test):
(.test):
* tests/stress/weird-setter-counter.js: Added.
(foo):
(test):
* tests/stress/weird-setter-counter-syntactic.js: Added.
(foo):
(test):
2014-07-01 Matthew Mirman <mmirman@apple.com>
Added an implementation of the "in" check to FTL.
https://bugs.webkit.org/show_bug.cgi?id=134508
Reviewed by Filip Pizlo.
* ftl/FTLCapabilities.cpp: enabled compilation for "in"
(JSC::FTL::canCompile): ditto
* ftl/FTLCompile.cpp:
(JSC::FTL::generateCheckInICFastPath): added.
(JSC::FTL::fixFunctionBasedOnStackMaps): added case for CheckIn descriptors.
* ftl/FTLInlineCacheDescriptor.h:
(JSC::FTL::CheckInGenerator::CheckInGenerator): added.
(JSC::FTL::CheckInDescriptor::CheckInDescriptor): added.
* ftl/FTLInlineCacheSize.cpp:
(JSC::FTL::sizeOfCheckIn): added. Currently larger than necessary.
* ftl/FTLInlineCacheSize.h: ditto
* ftl/FTLIntrinsicRepository.h: Added function type for operationInGeneric
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::LowerDFGToLLVM::compileNode): added case for In.
(JSC::FTL::LowerDFGToLLVM::compileIn): added.
* ftl/FTLSlowPathCall.cpp: Added a callOperation for operationIn
(JSC::FTL::callOperation): ditto
* ftl/FTLSlowPathCall.h: ditto
* ftl/FTLState.h: Added a vector to hold CheckIn descriptors.
* jit/JITOperations.h: made operationIns internal.
* tests/stress/ftl-checkin.js: Added.
* tests/stress/ftl-checkin-variable.js: Added.
2014-06-30 Mark Hahnenberg <mhahnenberg@apple.com>
CodeBlock::stronglyVisitWeakReferences should mark DFG::CommonData::weakStructureReferences
https://bugs.webkit.org/show_bug.cgi?id=134455
Reviewed by Geoffrey Garen.
Otherwise we get hanging pointers which can cause us to die later.
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::stronglyVisitWeakReferences):
2014-06-27 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Reduce the GC's influence on optimization decisions
https://bugs.webkit.org/show_bug.cgi?id=134427
Reviewed by Oliver Hunt.
This is a slight speed-up on some platforms, that arises from a bunch of fixes that I made
while trying to make the GC keep more structures alive
(https://bugs.webkit.org/show_bug.cgi?id=128072).
The fixes are, roughly:
- If the GC clears an inline cache, then this no longer causes the IC to be forever
polymorphic.
- If we exit in inlined code into a function that tries to OSR enter, then we jettison
sooner.
- Some variables being uninitialized led to rage-recompilations.
This is a pretty strong step in the direction of keeping more Structures alive and not
blowing away code just because a Structure died. But, it seems like there is still a slight
speed-up to be had from blowing away code that references dead Structures.
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::dumpAssumingJITType):
(JSC::shouldMarkTransition):
(JSC::CodeBlock::propagateTransitions):
(JSC::CodeBlock::determineLiveness):
* bytecode/GetByIdStatus.cpp:
(JSC::GetByIdStatus::computeForStubInfo):
* bytecode/PutByIdStatus.cpp:
(JSC::PutByIdStatus::computeForStubInfo):
* dfg/DFGCapabilities.cpp:
(JSC::DFG::isSupportedForInlining):
(JSC::DFG::mightInlineFunctionForCall):
(JSC::DFG::mightInlineFunctionForClosureCall):
(JSC::DFG::mightInlineFunctionForConstruct):
* dfg/DFGCapabilities.h:
* dfg/DFGCommonData.h:
* dfg/DFGDesiredWeakReferences.cpp:
(JSC::DFG::DesiredWeakReferences::reallyAdd):
* dfg/DFGOSREntry.cpp:
(JSC::DFG::prepareOSREntry):
* dfg/DFGOSRExitCompilerCommon.cpp:
(JSC::DFG::handleExitCounts):
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* ftl/FTLForOSREntryJITCode.cpp:
(JSC::FTL::ForOSREntryJITCode::ForOSREntryJITCode): These variables being uninitialized is benign in terms of correctness but can sometimes cause rage-recompilations. For some reason it took this patch to reveal this.
* ftl/FTLOSREntry.cpp:
(JSC::FTL::prepareOSREntry):
* runtime/Executable.cpp:
(JSC::ExecutableBase::destroy):
(JSC::NativeExecutable::destroy):
(JSC::ScriptExecutable::ScriptExecutable):
(JSC::ScriptExecutable::destroy):
(JSC::ScriptExecutable::installCode):
(JSC::EvalExecutable::EvalExecutable):
(JSC::ProgramExecutable::ProgramExecutable):
* runtime/Executable.h:
(JSC::ScriptExecutable::setDidTryToEnterInLoop):
(JSC::ScriptExecutable::didTryToEnterInLoop):
(JSC::ScriptExecutable::addressOfDidTryToEnterInLoop):
(JSC::ScriptExecutable::ScriptExecutable): Deleted.
* runtime/StructureInlines.h:
(JSC::Structure::storedPrototypeObject):
(JSC::Structure::storedPrototypeStructure):
2014-06-25 Filip Pizlo <fpizlo@apple.com>
[ftlopt] If a CodeBlock is jettisoned due to a watchpoint then it should be possible to figure out something about that watchpoint
https://bugs.webkit.org/show_bug.cgi?id=134333
Reviewed by Geoffrey Garen.
This is engineered to provide loads of information to the profiler without incurring any
costs when the profiler is disabled. It's the oldest trick in the book: the thing that
fires the watchpoint doesn't actually create anything to describe the reason why it was
fired; instead it creates a stack-allocated FireDetail subclass instance. Only if the
FireDetail::dump() virtual method is called does anything happen.
Currently we use this to produce very fine-grained data for Structure watchpoints and
some cases of variable watchpoints. For all other situations, the given reason is just a
string constant, by using StringFireDetail. If we find a situation where that string
constant is insufficient to diagnose an issue then we can change it to provide more
fine-grained information.
* JavaScriptCore.xcodeproj/project.pbxproj:
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::CodeBlock):
(JSC::CodeBlock::jettison):
* bytecode/CodeBlock.h:
* bytecode/CodeBlockJettisoningWatchpoint.cpp:
(JSC::CodeBlockJettisoningWatchpoint::fireInternal):
* bytecode/CodeBlockJettisoningWatchpoint.h:
* bytecode/ProfiledCodeBlockJettisoningWatchpoint.cpp: Removed.
* bytecode/ProfiledCodeBlockJettisoningWatchpoint.h: Removed.
* bytecode/StructureStubClearingWatchpoint.cpp:
(JSC::StructureStubClearingWatchpoint::fireInternal):
* bytecode/StructureStubClearingWatchpoint.h:
* bytecode/VariableWatchpointSet.h:
(JSC::VariableWatchpointSet::invalidate):
(JSC::VariableWatchpointSet::finalizeUnconditionally):
* bytecode/VariableWatchpointSetInlines.h:
(JSC::VariableWatchpointSet::notifyWrite):
* bytecode/Watchpoint.cpp:
(JSC::StringFireDetail::dump):
(JSC::WatchpointSet::fireAll):
(JSC::WatchpointSet::fireAllSlow):
(JSC::WatchpointSet::fireAllWatchpoints):
(JSC::InlineWatchpointSet::fireAll):
* bytecode/Watchpoint.h:
(JSC::FireDetail::FireDetail):
(JSC::FireDetail::~FireDetail):
(JSC::StringFireDetail::StringFireDetail):
(JSC::Watchpoint::fire):
(JSC::WatchpointSet::fireAll):
(JSC::WatchpointSet::touch):
(JSC::WatchpointSet::invalidate):
(JSC::InlineWatchpointSet::fireAll):
(JSC::InlineWatchpointSet::touch):
* dfg/DFGCommonData.h:
* dfg/DFGOperations.cpp:
* interpreter/Interpreter.cpp:
(JSC::Interpreter::execute):
* jsc.cpp:
(WTF::Masquerader::create):
* profiler/ProfilerCompilation.cpp:
(JSC::Profiler::Compilation::setJettisonReason):
(JSC::Profiler::Compilation::toJS):
* profiler/ProfilerCompilation.h:
(JSC::Profiler::Compilation::setJettisonReason): Deleted.
* runtime/ArrayBuffer.cpp:
(JSC::ArrayBuffer::transfer):
* runtime/ArrayBufferNeuteringWatchpoint.cpp:
(JSC::ArrayBufferNeuteringWatchpoint::fireAll):
* runtime/ArrayBufferNeuteringWatchpoint.h:
* runtime/CommonIdentifiers.h:
* runtime/CommonSlowPaths.cpp:
(JSC::SLOW_PATH_DECL):
* runtime/Identifier.cpp:
(JSC::Identifier::dump):
* runtime/Identifier.h:
* runtime/JSFunction.cpp:
(JSC::JSFunction::put):
(JSC::JSFunction::defineOwnProperty):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::addFunction):
(JSC::JSGlobalObject::haveABadTime):
* runtime/JSSymbolTableObject.cpp:
(JSC::VariableWriteFireDetail::dump):
* runtime/JSSymbolTableObject.h:
(JSC::VariableWriteFireDetail::VariableWriteFireDetail):
(JSC::symbolTablePut):
(JSC::symbolTablePutWithAttributes):
* runtime/PropertyName.h:
(JSC::PropertyName::dump):
* runtime/Structure.cpp:
(JSC::Structure::notifyTransitionFromThisStructure):
* runtime/Structure.h:
(JSC::Structure::notifyTransitionFromThisStructure): Deleted.
* runtime/SymbolTable.cpp:
(JSC::SymbolTableEntry::notifyWriteSlow):
(JSC::SymbolTable::WatchpointCleanup::finalizeUnconditionally):
* runtime/SymbolTable.h:
(JSC::SymbolTableEntry::notifyWrite):
* runtime/VM.cpp:
(JSC::VM::addImpureProperty):
Source/WebCore:
2014-07-01 Mark Lam <mark.lam@apple.com>
[ftlopt] DebuggerCallFrame::scope() should return a DebuggerScope.
<https://webkit.org/b/134420>
Reviewed by Geoffrey Garen.
No new tests.
* ForwardingHeaders/debugger/DebuggerCallFrame.h: Removed.
- This is not in use. Hence, we can remove it.
* bindings/js/ScriptController.cpp:
(WebCore::ScriptController::attachDebugger):
- We should acquire the JSLock before modifying a JS global object.
2014-06-25 Filip Pizlo <fpizlo@apple.com>
[ftlopt] If a CodeBlock is jettisoned due to a watchpoint then it should be possible to figure out something about that watchpoint
https://bugs.webkit.org/show_bug.cgi?id=134333
Reviewed by Geoffrey Garen.
No new tests because no change in behavior.
* bindings/scripts/CodeGeneratorJS.pm:
(GenerateHeader):
Tools:
2014-06-25 Filip Pizlo <fpizlo@apple.com>
[ftlopt] If a CodeBlock is jettisoned due to a watchpoint then it should be possible to figure out something about that watchpoint
https://bugs.webkit.org/show_bug.cgi?id=134333
Reviewed by Geoffrey Garen.
* Scripts/display-profiler-output:
LayoutTests:
2014-07-16 Mark Hahnenberg <mhahnenberg@apple.com>
sputnik/Implementation_Diagnostics/S12.6.4_D1.html depends on undefined behavior
https://bugs.webkit.org/show_bug.cgi?id=135007
Reviewed by Filip Pizlo.
EcmaScript 5.1 specifies that during for-in enumeration newly added properties may or may not be
visited during the current enumeration. Specifically, in section 12.6.4 the spec states:
"If new properties are added to the object being enumerated during enumeration, the newly added properties
are not guaranteed to be visited in the active enumeration."
The sputnik/Implementation_Diagnostics/S12.6.4_D1.html layout test is from before sputnik was added
to the test262 suite. I believe it has since been removed, so it would probably be okay to remove it
from our layout test suite.
* sputnik/Implementation_Diagnostics/S12.6.4_D1-expected.txt: Removed.
* sputnik/Implementation_Diagnostics/S12.6.4_D1.html: Removed.
2014-07-13 Filip Pizlo <fpizlo@apple.com>
[ftlopt] DFG should be able to do GCSE in SSA and this should be unified with the CSE in CPS, and both of these things should use abstract heaps for reasoning about effects
https://bugs.webkit.org/show_bug.cgi?id=134677
Reviewed by Sam Weinig.
* js/regress/gcse-expected.txt: Added.
* js/regress/gcse-poly-get-expected.txt: Added.
* js/regress/gcse-poly-get-less-obvious-expected.txt: Added.
* js/regress/gcse-poly-get-less-obvious.html: Added.
* js/regress/gcse-poly-get.html: Added.
* js/regress/gcse.html: Added.
* js/regress/script-tests/gcse-poly-get-less-obvious.js: Added.
* js/regress/script-tests/gcse-poly-get.js: Added.
* js/regress/script-tests/gcse.js: Added.
2014-07-04 Filip Pizlo <fpizlo@apple.com>
[ftlopt] Infer immutable object properties
https://bugs.webkit.org/show_bug.cgi?id=134567
Reviewed by Mark Hahnenberg.
* js/regress/infer-constant-global-property-expected.txt: Added.
* js/regress/infer-constant-global-property.html: Added.
* js/regress/infer-constant-property-expected.txt: Added.
* js/regress/infer-constant-property.html: Added.
* js/regress/script-tests/infer-constant-global-property.js: Added.
* js/regress/script-tests/infer-constant-property.js: Added.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@172129 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractHeap.h b/Source/JavaScriptCore/dfg/DFGAbstractHeap.h
index 9507653..68380c0 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractHeap.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractHeap.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -42,31 +42,22 @@
#define FOR_EACH_ABSTRACT_HEAP_KIND(macro) \
macro(InvalidAbstractHeap) \
macro(World) \
- macro(Arguments_numArguments) \
- macro(Arguments_overrideLength) \
macro(Arguments_registers) \
- macro(Arguments_slowArguments) \
- macro(ArrayBuffer_data) \
- macro(Butterfly_arrayBuffer) \
macro(Butterfly_publicLength) \
macro(Butterfly_vectorLength) \
macro(GetterSetter_getter) \
macro(GetterSetter_setter) \
- macro(JSArrayBufferView_length) \
- macro(JSArrayBufferView_mode) \
- macro(JSArrayBufferView_vector) \
macro(JSCell_structureID) \
macro(JSCell_indexingType) \
macro(JSCell_typeInfoFlags) \
macro(JSCell_typeInfoType) \
- macro(JSFunction_executable) \
- macro(JSFunction_scopeChain) \
macro(JSObject_butterfly) \
macro(JSVariableObject_registers) \
macro(NamedProperties) \
macro(IndexedInt32Properties) \
macro(IndexedDoubleProperties) \
macro(IndexedContiguousProperties) \
+ macro(IndexedArrayStorageProperties) \
macro(ArrayStorageProperties) \
macro(Variables) \
macro(TypedArrayProperties) \
@@ -76,7 +67,7 @@
macro(Absolute) \
/* Use this for writes only, to indicate that this may fire watchpoints. Usually this is never directly written but instead we test to see if a node clobbers this; it just so happens that you have to write world to clobber it. */\
macro(Watchpoint_fire) \
- /* Use this for reads only, just to indicate that if the world got clobbered, then this operation will not work. */\
+ /* Use these for reads only, just to indicate that if the world got clobbered, then this operation will not work. */\
macro(MiscFields) \
/* Use this for writes only, just to indicate that hoisting the node is invalid. This works because we don't hoist anything that has any side effects at all. */\
macro(SideState)
@@ -207,7 +198,7 @@
return payloadImpl();
}
- bool isDisjoint(const AbstractHeap& other)
+ bool isDisjoint(const AbstractHeap& other) const
{
ASSERT(kind() != InvalidAbstractHeap);
ASSERT(other.kind() != InvalidAbstractHeap);
@@ -220,7 +211,7 @@
return payload().isDisjoint(other.payload());
}
- bool overlaps(const AbstractHeap& other)
+ bool overlaps(const AbstractHeap& other) const
{
return !isDisjoint(other);
}
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
index 60c477d..0ae42fd 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
@@ -30,6 +30,7 @@
#include "DFGAbstractInterpreter.h"
#include "GetByIdStatus.h"
+#include "GetterSetter.h"
#include "Operations.h"
#include "PutByIdStatus.h"
#include "StringObject.h"
@@ -1354,11 +1355,35 @@
break;
case GetCallee:
- case GetGetter:
- case GetSetter:
forNode(node).setType(SpecFunction);
break;
+ case GetGetter: {
+ JSValue base = forNode(node->child1()).m_value;
+ if (base) {
+ if (JSObject* getter = jsCast<GetterSetter*>(base)->getterConcurrently()) {
+ setConstant(node, *m_graph.freeze(getter));
+ break;
+ }
+ }
+
+ forNode(node).setType(SpecObject);
+ break;
+ }
+
+ case GetSetter: {
+ JSValue base = forNode(node->child1()).m_value;
+ if (base) {
+ if (JSObject* setter = jsCast<GetterSetter*>(base)->setterConcurrently()) {
+ setConstant(node, *m_graph.freeze(setter));
+ break;
+ }
+ }
+
+ forNode(node).setType(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:
@@ -1405,14 +1430,17 @@
// something more subtle?
AbstractValue result;
for (unsigned i = status.numVariants(); i--;) {
- if (!status[i].specificValue()) {
+ DFG_ASSERT(m_graph, node, !status[i].alternateBase());
+ JSValue constantResult =
+ m_graph.tryGetConstantProperty(value, status[i].offset());
+ if (!constantResult) {
result.makeHeapTop();
break;
}
AbstractValue thisResult;
thisResult.set(
- m_graph, *m_graph.freeze(status[i].specificValue()),
+ m_graph, *m_graph.freeze(constantResult),
m_state.structureClobberState());
result.merge(thisResult);
}
@@ -1587,11 +1615,25 @@
}
case GetByOffset: {
+ StorageAccessData data = m_graph.m_storageAccessData[node->storageAccessDataIndex()];
+ JSValue result = m_graph.tryGetConstantProperty(forNode(node->child2()), data.offset);
+ if (result) {
+ setConstant(node, *m_graph.freeze(result));
+ break;
+ }
+
forNode(node).makeHeapTop();
break;
}
case GetGetterSetterByOffset: {
+ StorageAccessData data = m_graph.m_storageAccessData[node->storageAccessDataIndex()];
+ JSValue result = m_graph.tryGetConstantProperty(forNode(node->child2()), data.offset);
+ if (result && jsDynamicCast<GetterSetter*>(result)) {
+ setConstant(node, *m_graph.freeze(result));
+ break;
+ }
+
forNode(node).set(m_graph, m_graph.m_vm.getterSetterStructure.get());
break;
}
@@ -1620,14 +1662,23 @@
if (set.isEmpty())
continue;
baseSet.merge(set);
- if (!variant.specificValue()) {
+
+ JSValue baseForLoad;
+ if (variant.alternateBase())
+ baseForLoad = variant.alternateBase();
+ else
+ baseForLoad = base.m_value;
+ JSValue constantResult =
+ m_graph.tryGetConstantProperty(
+ baseForLoad, variant.baseStructure(), variant.offset());
+ if (!constantResult) {
result.makeHeapTop();
continue;
}
AbstractValue thisResult;
thisResult.set(
m_graph,
- *m_graph.freeze(variant.specificValue()),
+ *m_graph.freeze(constantResult),
m_state.structureClobberState());
result.merge(thisResult);
}
diff --git a/Source/JavaScriptCore/dfg/DFGAdjacencyList.h b/Source/JavaScriptCore/dfg/DFGAdjacencyList.h
index 6bbfefd..358ac79 100644
--- a/Source/JavaScriptCore/dfg/DFGAdjacencyList.h
+++ b/Source/JavaScriptCore/dfg/DFGAdjacencyList.h
@@ -52,7 +52,7 @@
}
}
- AdjacencyList(Kind kind, Edge child1, Edge child2, Edge child3)
+ AdjacencyList(Kind kind, Edge child1, Edge child2 = Edge(), Edge child3 = Edge())
{
ASSERT_UNUSED(kind, kind == Fixed);
initialize(child1, child2, child3);
@@ -132,7 +132,7 @@
setChild(i, child(i + 1));
setChild(Size - 1, Edge());
}
-
+
unsigned firstChild() const
{
return m_words[0].m_encodedWord;
@@ -151,6 +151,41 @@
m_words[1].m_encodedWord = numChildren;
}
+ AdjacencyList sanitized() const
+ {
+ return AdjacencyList(Fixed, child1().sanitized(), child2().sanitized(), child3().sanitized());
+ }
+
+ unsigned hash() const
+ {
+ unsigned result = 0;
+ if (!child1())
+ return result;
+
+ result += child1().hash();
+
+ if (!child2())
+ return result;
+
+ result *= 3;
+ result += child2().hash();
+
+ if (!child3())
+ return result;
+
+ result *= 3;
+ result += child3().hash();
+
+ return result;
+ }
+
+ bool operator==(const AdjacencyList& other) const
+ {
+ return child1() == other.child1()
+ && child2() == other.child2()
+ && child3() == other.child3();
+ }
+
private:
Edge m_words[Size];
};
diff --git a/Source/JavaScriptCore/dfg/DFGBasicBlock.h b/Source/JavaScriptCore/dfg/DFGBasicBlock.h
index a648a89..8099407 100644
--- a/Source/JavaScriptCore/dfg/DFGBasicBlock.h
+++ b/Source/JavaScriptCore/dfg/DFGBasicBlock.h
@@ -171,8 +171,8 @@
SSAData(BasicBlock*);
~SSAData();
};
- OwnPtr<SSAData> ssa;
-
+ std::unique_ptr<SSAData> ssa;
+
private:
friend class InsertionSet;
Vector<Node*, 8> m_nodes;
diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
index 087f84d..8d9dae8 100644
--- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
+++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
@@ -171,6 +171,10 @@
// Handle calls. This resolves issues surrounding inlining and intrinsics.
void handleCall(
int result, NodeType op, InlineCallFrame::Kind, unsigned instructionSize,
+ Node* callTarget, int argCount, int registerOffset, CallLinkStatus,
+ SpeculatedType prediction);
+ void handleCall(
+ int result, NodeType op, InlineCallFrame::Kind, unsigned instructionSize,
Node* callTarget, int argCount, int registerOffset, CallLinkStatus);
void handleCall(int result, NodeType op, CodeSpecializationKind, unsigned instructionSize, int callee, int argCount, int registerOffset);
void handleCall(Instruction* pc, NodeType op, CodeSpecializationKind);
@@ -183,7 +187,7 @@
bool handleTypedArrayConstructor(int resultOperand, InternalFunction*, int registerOffset, int argumentCountIncludingThis, TypedArrayType);
bool handleConstantInternalFunction(int resultOperand, InternalFunction*, int registerOffset, int argumentCountIncludingThis, SpeculatedType prediction, CodeSpecializationKind);
Node* handlePutByOffset(Node* base, unsigned identifier, PropertyOffset, Node* value);
- Node* handleGetByOffset(SpeculatedType, Node* base, unsigned identifierNumber, PropertyOffset, NodeType op = GetByOffset);
+ Node* handleGetByOffset(SpeculatedType, Node* base, const StructureSet&, unsigned identifierNumber, PropertyOffset, NodeType op = GetByOffset);
void handleGetById(
int destinationOperand, SpeculatedType, Node* base, unsigned identifierNumber,
const GetByIdStatus&);
@@ -640,10 +644,10 @@
m_numPassedVarArgs++;
}
- Node* addCall(int result, NodeType op, Node* callee, int argCount, int registerOffset)
+ Node* addCallWithoutSettingResult(
+ NodeType op, Node* callee, int argCount, int registerOffset,
+ SpeculatedType prediction)
{
- SpeculatedType prediction = getPrediction();
-
addVarArgChild(callee);
size_t parameterSlots = JSStack::CallFrameHeaderSize - JSStack::CallerFrameAndPCSize + argCount;
if (parameterSlots > m_parameterSlots)
@@ -653,8 +657,18 @@
for (int i = 0 + dummyThisArgument; i < argCount; ++i)
addVarArgChild(get(virtualRegisterForArgument(i, registerOffset)));
- Node* call = addToGraph(Node::VarArg, op, OpInfo(0), OpInfo(prediction));
- set(VirtualRegister(result), call);
+ return addToGraph(Node::VarArg, op, OpInfo(0), OpInfo(prediction));
+ }
+
+ Node* addCall(
+ int result, NodeType op, Node* callee, int argCount, int registerOffset,
+ SpeculatedType prediction)
+ {
+ Node* call = addCallWithoutSettingResult(
+ op, callee, argCount, registerOffset, prediction);
+ VirtualRegister resultReg(result);
+ if (resultReg.isValid())
+ set(VirtualRegister(result), call);
return call;
}
@@ -994,6 +1008,16 @@
Node* callTarget, int argumentCountIncludingThis, int registerOffset,
CallLinkStatus callLinkStatus)
{
+ handleCall(
+ result, op, kind, instructionSize, callTarget, argumentCountIncludingThis,
+ registerOffset, callLinkStatus, getPrediction());
+}
+
+void ByteCodeParser::handleCall(
+ int result, NodeType op, InlineCallFrame::Kind kind, unsigned instructionSize,
+ Node* callTarget, int argumentCountIncludingThis, int registerOffset,
+ CallLinkStatus callLinkStatus, SpeculatedType prediction)
+{
ASSERT(registerOffset <= 0);
CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind);
@@ -1004,12 +1028,11 @@
// Oddly, this conflates calls that haven't executed with calls that behaved sufficiently polymorphically
// that we cannot optimize them.
- addCall(result, op, callTarget, argumentCountIncludingThis, registerOffset);
+ addCall(result, op, callTarget, argumentCountIncludingThis, registerOffset, prediction);
return;
}
unsigned nextOffset = m_currentIndex + instructionSize;
- SpeculatedType prediction = getPrediction();
if (InternalFunction* function = callLinkStatus.internalFunction()) {
if (handleConstantInternalFunction(result, function, registerOffset, argumentCountIncludingThis, prediction, specializationKind)) {
@@ -1021,7 +1044,7 @@
}
// Can only handle this using the generic call handler.
- addCall(result, op, callTarget, argumentCountIncludingThis, registerOffset);
+ addCall(result, op, callTarget, argumentCountIncludingThis, registerOffset, prediction);
return;
}
@@ -1058,7 +1081,7 @@
}
}
}
- Node* call = addCall(result, op, callTarget, argumentCountIncludingThis, registerOffset);
+ Node* call = addCall(result, op, callTarget, argumentCountIncludingThis, registerOffset, prediction);
if (knownFunction)
call->giveKnownFunction(knownFunction);
@@ -1205,9 +1228,12 @@
size_t argumentPositionStart = m_graph.m_argumentPositions.size();
+ VirtualRegister resultReg(resultOperand);
+ if (resultReg.isValid())
+ resultReg = m_inlineStackTop->remapOperand(resultReg);
+
InlineStackEntry inlineStackEntry(
- this, codeBlock, codeBlock, m_graph.lastBlock(), callLinkStatus.function(),
- m_inlineStackTop->remapOperand(VirtualRegister(resultOperand)),
+ this, codeBlock, codeBlock, m_graph.lastBlock(), callLinkStatus.function(), resultReg,
(VirtualRegister)inlineCallFrameStart, argumentCountIncludingThis, kind);
// This is where the actual inlining really happens.
@@ -1673,8 +1699,15 @@
return false;
}
-Node* ByteCodeParser::handleGetByOffset(SpeculatedType prediction, Node* base, unsigned identifierNumber, PropertyOffset offset, NodeType op)
+Node* ByteCodeParser::handleGetByOffset(SpeculatedType prediction, Node* base, const StructureSet& structureSet, unsigned identifierNumber, PropertyOffset offset, NodeType op)
{
+ if (base->hasConstant()) {
+ if (JSValue constant = m_graph.tryGetConstantProperty(base->asJSValue(), structureSet, offset)) {
+ addToGraph(Phantom, base);
+ return weakJSConstant(constant);
+ }
+ }
+
Node* propertyStorage;
if (isInlineOffset(offset))
propertyStorage = base;
@@ -1769,20 +1802,14 @@
// Unless we want bugs like https://bugs.webkit.org/show_bug.cgi?id=88783, we need to
// ensure that the base of the original get_by_id is kept alive until we're done with
// all of the speculations. We only insert the Phantom if there had been a CheckStructure
- // on something other than the base following the CheckStructure on base, or if the
- // access was compiled to a WeakJSConstant specific value, in which case we might not
- // have any explicit use of the base at all.
- if (variant.specificValue() || originalBase != base)
+ // on something other than the base following the CheckStructure on base.
+ if (originalBase != base)
addToGraph(Phantom, originalBase);
- Node* loadedValue;
- if (variant.specificValue())
- loadedValue = weakJSConstant(variant.specificValue());
- else {
- loadedValue = handleGetByOffset(
- prediction, base, identifierNumber, variant.offset(),
- variant.callLinkStatus() ? GetGetterSetterByOffset : GetByOffset);
- }
+ Node* loadedValue = handleGetByOffset(
+ variant.callLinkStatus() ? SpecCellOther : prediction,
+ base, variant.baseStructure(), identifierNumber, variant.offset(),
+ variant.callLinkStatus() ? GetGetterSetterByOffset : GetByOffset);
if (!variant.callLinkStatus()) {
set(VirtualRegister(destinationOperand), loadedValue);
@@ -1824,7 +1851,7 @@
handleCall(
destinationOperand, Call, InlineCallFrame::GetterCall, OPCODE_LENGTH(op_get_by_id),
- getter, numberOfParameters - 1, registerOffset, *variant.callLinkStatus());
+ getter, numberOfParameters - 1, registerOffset, *variant.callLinkStatus(), prediction);
}
void ByteCodeParser::emitPutById(
@@ -1875,7 +1902,8 @@
ASSERT(putByIdStatus.numVariants() == 1);
const PutByIdVariant& variant = putByIdStatus[0];
- if (variant.kind() == PutByIdVariant::Replace) {
+ switch (variant.kind()) {
+ case PutByIdVariant::Replace: {
addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(variant.structure())), base);
handlePutByOffset(base, identifierNumber, variant.offset(), value);
if (m_graph.compilation())
@@ -1883,57 +1911,111 @@
return;
}
- if (variant.kind() != PutByIdVariant::Transition) {
- emitPutById(base, identifierNumber, value, putByIdStatus, isDirect);
+ case PutByIdVariant::Transition: {
+ addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(variant.oldStructure())), base);
+ emitChecks(variant.constantChecks());
+
+ ASSERT(variant.oldStructureForTransition()->transitionWatchpointSetHasBeenInvalidated());
+
+ Node* propertyStorage;
+ Transition* transition = m_graph.m_transitions.add(
+ variant.oldStructureForTransition(), variant.newStructure());
+
+ if (variant.reallocatesStorage()) {
+
+ // If we're growing the property storage then it must be because we're
+ // storing into the out-of-line storage.
+ ASSERT(!isInlineOffset(variant.offset()));
+
+ if (!variant.oldStructureForTransition()->outOfLineCapacity()) {
+ propertyStorage = addToGraph(
+ AllocatePropertyStorage, OpInfo(transition), base);
+ } else {
+ propertyStorage = addToGraph(
+ ReallocatePropertyStorage, OpInfo(transition),
+ base, addToGraph(GetButterfly, base));
+ }
+ } else {
+ if (isInlineOffset(variant.offset()))
+ propertyStorage = base;
+ else
+ propertyStorage = addToGraph(GetButterfly, base);
+ }
+
+ addToGraph(PutStructure, OpInfo(transition), base);
+
+ addToGraph(
+ PutByOffset,
+ OpInfo(m_graph.m_storageAccessData.size()),
+ propertyStorage,
+ base,
+ value);
+
+ StorageAccessData storageAccessData;
+ storageAccessData.offset = variant.offset();
+ storageAccessData.identifierNumber = identifierNumber;
+ m_graph.m_storageAccessData.append(storageAccessData);
+
+ if (m_graph.compilation())
+ m_graph.compilation()->noticeInlinedPutById();
return;
}
-
- addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(variant.oldStructure())), base);
- emitChecks(variant.constantChecks());
-
- ASSERT(variant.oldStructureForTransition()->transitionWatchpointSetHasBeenInvalidated());
+
+ case PutByIdVariant::Setter: {
+ Node* originalBase = base;
+
+ addToGraph(
+ CheckStructure, OpInfo(m_graph.addStructureSet(variant.structure())), base);
+
+ emitChecks(variant.constantChecks());
+
+ if (variant.alternateBase())
+ base = weakJSConstant(variant.alternateBase());
+
+ Node* loadedValue = handleGetByOffset(
+ SpecCellOther, base, variant.baseStructure(), identifierNumber, variant.offset(),
+ GetGetterSetterByOffset);
+
+ Node* setter = addToGraph(GetSetter, loadedValue);
+
+ // Make a call. We don't try to get fancy with using the smallest operand number because
+ // the stack layout phase should compress the stack anyway.
- Node* propertyStorage;
- Transition* transition = m_graph.m_transitions.add(
- variant.oldStructureForTransition(), variant.newStructure());
-
- if (variant.reallocatesStorage()) {
-
- // If we're growing the property storage then it must be because we're
- // storing into the out-of-line storage.
- ASSERT(!isInlineOffset(variant.offset()));
-
- if (!variant.oldStructureForTransition()->outOfLineCapacity()) {
- propertyStorage = addToGraph(
- AllocatePropertyStorage, OpInfo(transition), base);
- } else {
- propertyStorage = addToGraph(
- ReallocatePropertyStorage, OpInfo(transition),
- base, addToGraph(GetButterfly, base));
- }
- } else {
- if (isInlineOffset(variant.offset()))
- propertyStorage = base;
- else
- propertyStorage = addToGraph(GetButterfly, base);
+ unsigned numberOfParameters = 0;
+ numberOfParameters++; // The 'this' argument.
+ numberOfParameters++; // The new value.
+ numberOfParameters++; // True return PC.
+
+ // Start with a register offset that corresponds to the last in-use register.
+ int registerOffset = virtualRegisterForLocal(
+ m_inlineStackTop->m_profiledBlock->m_numCalleeRegisters - 1).offset();
+ registerOffset -= numberOfParameters;
+ registerOffset -= JSStack::CallFrameHeaderSize;
+
+ // Get the alignment right.
+ registerOffset = -WTF::roundUpToMultipleOf(
+ stackAlignmentRegisters(),
+ -registerOffset);
+
+ ensureLocals(
+ m_inlineStackTop->remapOperand(
+ VirtualRegister(registerOffset)).toLocal());
+
+ int nextRegister = registerOffset + JSStack::CallFrameHeaderSize;
+ set(VirtualRegister(nextRegister++), originalBase, ImmediateNakedSet);
+ set(VirtualRegister(nextRegister++), value, ImmediateNakedSet);
+
+ handleCall(
+ VirtualRegister().offset(), Call, InlineCallFrame::SetterCall,
+ OPCODE_LENGTH(op_put_by_id), setter, numberOfParameters - 1, registerOffset,
+ *variant.callLinkStatus(), SpecOther);
+ return;
}
-
- addToGraph(PutStructure, OpInfo(transition), base);
-
- addToGraph(
- PutByOffset,
- OpInfo(m_graph.m_storageAccessData.size()),
- propertyStorage,
- base,
- value);
-
- StorageAccessData storageAccessData;
- storageAccessData.offset = variant.offset();
- storageAccessData.identifierNumber = identifierNumber;
- m_graph.m_storageAccessData.append(storageAccessData);
-
- if (m_graph.compilation())
- m_graph.compilation()->noticeInlinedPutById();
+
+ default: {
+ emitPutById(base, identifierNumber, value, putByIdStatus, isDirect);
+ return;
+ } }
}
void ByteCodeParser::prepareToParseBlock()
@@ -2715,8 +2797,8 @@
case op_ret:
flushForReturn();
if (inlineCallFrame()) {
- ASSERT(m_inlineStackTop->m_returnValue.isValid());
- setDirect(m_inlineStackTop->m_returnValue, get(VirtualRegister(currentInstruction[1].u.operand)), ImmediateSetWithFlush);
+ if (m_inlineStackTop->m_returnValue.isValid())
+ setDirect(m_inlineStackTop->m_returnValue, get(VirtualRegister(currentInstruction[1].u.operand)), ImmediateSetWithFlush);
m_inlineStackTop->m_didReturn = true;
if (m_inlineStackTop->m_unlinkedBlocks.isEmpty()) {
// If we're returning from the first block, then we're done parsing.
@@ -2894,10 +2976,7 @@
}
Node* base = cellConstantWithStructureCheck(globalObject, status[0].structureSet().onlyStructure());
addToGraph(Phantom, get(VirtualRegister(scope)));
- if (JSValue specificValue = status[0].specificValue())
- set(VirtualRegister(dst), weakJSConstant(specificValue.asCell()));
- else
- set(VirtualRegister(dst), handleGetByOffset(prediction, base, identifierNumber, operand));
+ set(VirtualRegister(dst), handleGetByOffset(prediction, base, status[0].structureSet(), identifierNumber, operand));
break;
}
case GlobalVar:
@@ -2905,16 +2984,16 @@
addToGraph(Phantom, get(VirtualRegister(scope)));
SymbolTableEntry entry = globalObject->symbolTable()->get(uid);
VariableWatchpointSet* watchpointSet = entry.watchpointSet();
- JSValue specificValue =
+ JSValue inferredValue =
watchpointSet ? watchpointSet->inferredValue() : JSValue();
- if (!specificValue) {
+ if (!inferredValue) {
SpeculatedType prediction = getPrediction();
set(VirtualRegister(dst), addToGraph(GetGlobalVar, OpInfo(operand), OpInfo(prediction)));
break;
}
addToGraph(VariableWatchpoint, OpInfo(watchpointSet));
- set(VirtualRegister(dst), weakJSConstant(specificValue));
+ set(VirtualRegister(dst), weakJSConstant(inferredValue));
break;
}
case ClosureVar:
diff --git a/Source/JavaScriptCore/dfg/DFGCPSRethreadingPhase.cpp b/Source/JavaScriptCore/dfg/DFGCPSRethreadingPhase.cpp
index 076ba25..80d49c1 100644
--- a/Source/JavaScriptCore/dfg/DFGCPSRethreadingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGCPSRethreadingPhase.cpp
@@ -201,12 +201,12 @@
if (otherNode->op() == GetLocal) {
// Replace all references to this GetLocal with otherNode.
- node->misc.replacement = otherNode;
+ node->replacement = otherNode;
return;
}
ASSERT(otherNode->op() == SetLocal);
- node->misc.replacement = otherNode->child1().node();
+ node->replacement = otherNode->child1().node();
return;
}
diff --git a/Source/JavaScriptCore/dfg/DFGCSEPhase.cpp b/Source/JavaScriptCore/dfg/DFGCSEPhase.cpp
index 8726b5a..f6d7df5 100644
--- a/Source/JavaScriptCore/dfg/DFGCSEPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGCSEPhase.cpp
@@ -29,6 +29,7 @@
#if ENABLE(DFG_JIT)
#include "DFGAbstractHeap.h"
+#include "DFGClobberSet.h"
#include "DFGClobberize.h"
#include "DFGEdgeUsesStructure.h"
#include "DFGGraph.h"
@@ -39,18 +40,52 @@
namespace JSC { namespace DFG {
-class CSEPhase : public Phase {
+// This file contains two CSE implementations: local and global. LocalCSE typically runs when we're
+// in DFG mode, i.e. we want to compile quickly. LocalCSE contains a lot of optimizations for
+// compile time. GlobalCSE, on the other hand, is fairly straight-forward. It will find more
+// optimization opportunities by virtue of being global.
+
+namespace {
+
+const bool verbose = false;
+
+class ClobberFilter {
public:
- CSEPhase(Graph& graph)
- : Phase(graph, "common subexpression elimination")
+ ClobberFilter(AbstractHeap heap)
+ : m_heap(heap)
+ {
+ }
+
+ bool operator()(const ImpureMap::KeyValuePairType& pair) const
+ {
+ return m_heap.overlaps(pair.key.heap());
+ }
+
+private:
+ AbstractHeap m_heap;
+};
+
+inline void clobber(ImpureMap& map, AbstractHeap heap)
+{
+ ClobberFilter filter(heap);
+ map.removeIf(filter);
+}
+
+class LocalCSEPhase : public Phase {
+public:
+ LocalCSEPhase(Graph& graph)
+ : Phase(graph, "local common subexpression elimination")
+ , m_smallBlock(graph)
+ , m_largeBlock(graph)
{
}
bool run()
{
- ASSERT(m_graph.m_fixpointState != BeforeFixpoint);
+ ASSERT(m_graph.m_fixpointState == FixpointNotConverged);
+ ASSERT(m_graph.m_form == ThreadedCPS || m_graph.m_form == LoadStore);
- m_changed = false;
+ bool changed = false;
m_graph.clearReplacements();
@@ -59,1177 +94,590 @@
if (!block)
continue;
- // All Phis need to already be marked as relevant to OSR.
- if (!ASSERT_DISABLED) {
- for (unsigned i = 0; i < block->phis.size(); ++i)
- ASSERT(block->phis[i]->flags() & NodeRelevantToOSR);
+ if (block->size() <= SmallMaps::capacity)
+ changed |= m_smallBlock.run(block);
+ else
+ changed |= m_largeBlock.run(block);
+ }
+
+ return changed;
+ }
+
+private:
+ class SmallMaps {
+ public:
+ // This permits SmallMaps to be used for blocks that have up to 100 nodes. In practice,
+ // fewer than half of the nodes in a block have pure defs, and even fewer have impure defs.
+ // Thus, a capacity limit of 100 probably means that somewhere around ~40 things may end up
+ // in one of these "small" list-based maps. That number still seems largeish, except that
+ // the overhead of HashMaps can be quite high currently: clearing them, or even removing
+ // enough things from them, deletes (or resizes) their backing store eagerly. Hence
+ // HashMaps induce a lot of malloc traffic.
+ static const unsigned capacity = 100;
+
+ SmallMaps()
+ : m_pureLength(0)
+ , m_impureLength(0)
+ {
+ }
+
+ void clear()
+ {
+ m_pureLength = 0;
+ m_impureLength = 0;
+ }
+
+ void write(AbstractHeap heap)
+ {
+ for (unsigned i = 0; i < m_impureLength; ++i) {
+ if (heap.overlaps(m_impureMap[i].key.heap()))
+ m_impureMap[i--] = m_impureMap[--m_impureLength];
}
+ }
+
+ Node* addPure(PureValue value, Node* node)
+ {
+ for (unsigned i = m_pureLength; i--;) {
+ if (m_pureMap[i].key == value)
+ return m_pureMap[i].value;
+ }
+
+ ASSERT(m_pureLength < capacity);
+ m_pureMap[m_pureLength++] = WTF::KeyValuePair<PureValue, Node*>(value, node);
+ return nullptr;
+ }
+
+ Node* findReplacement(HeapLocation location)
+ {
+ for (unsigned i = m_impureLength; i--;) {
+ if (m_impureMap[i].key == location)
+ return m_impureMap[i].value;
+ }
+ return nullptr;
+ }
+
+ Node* addImpure(HeapLocation location, Node* node)
+ {
+ if (Node* result = findReplacement(location))
+ return result;
+ ASSERT(m_impureLength < capacity);
+ m_impureMap[m_impureLength++] = WTF::KeyValuePair<HeapLocation, Node*>(location, node);
+ return nullptr;
+ }
+
+ private:
+ WTF::KeyValuePair<PureValue, Node*> m_pureMap[capacity];
+ WTF::KeyValuePair<HeapLocation, Node*> m_impureMap[capacity];
+ unsigned m_pureLength;
+ unsigned m_impureLength;
+ };
+
+ class LargeMaps {
+ public:
+ LargeMaps()
+ {
+ }
+
+ void clear()
+ {
+ m_pureMap.clear();
+ m_impureMap.clear();
+ }
+
+ void write(AbstractHeap heap)
+ {
+ clobber(m_impureMap, heap);
+ }
+
+ Node* addPure(PureValue value, Node* node)
+ {
+ auto result = m_pureMap.add(value, node);
+ if (result.isNewEntry)
+ return nullptr;
+ return result.iterator->value;
+ }
+
+ Node* findReplacement(HeapLocation location)
+ {
+ return m_impureMap.get(location);
+ }
+
+ Node* addImpure(HeapLocation location, Node* node)
+ {
+ auto result = m_impureMap.add(location, node);
+ if (result.isNewEntry)
+ return nullptr;
+ return result.iterator->value;
+ }
+
+ private:
+ HashMap<PureValue, Node*> m_pureMap;
+ HashMap<HeapLocation, Node*> m_impureMap;
+ };
+
+ template<typename Maps>
+ class BlockCSE {
+ public:
+ BlockCSE(Graph& graph)
+ : m_graph(graph)
+ {
+ }
+
+ bool run(BasicBlock* block)
+ {
+ m_maps.clear();
+ m_changed = false;
+
+ for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
+ m_node = block->at(nodeIndex);
+ m_graph.performSubstitution(m_node);
- for (unsigned i = block->size(); i--;) {
- Node* node = block->at(i);
-
- switch (node->op()) {
- case SetLocal:
- case GetLocal: // FIXME: The GetLocal case is only necessary until we do https://bugs.webkit.org/show_bug.cgi?id=106707.
- node->mergeFlags(NodeRelevantToOSR);
- break;
- default:
- node->clearFlags(NodeRelevantToOSR);
- break;
+ if (m_node->op() == Identity) {
+ m_node->convertToCheck();
+ m_node->replacement = m_node->child1().node();
+ m_changed = true;
+ } else {
+ // This rule only makes sense for local CSE, since in SSA form we have already
+ // factored the bounds check out of the PutByVal. It's kind of gross, but we
+ // still have reason to believe that PutByValAlias is a good optimization and
+ // that it's better to do it with a single node rather than separating out the
+ // CheckInBounds.
+ if (m_node->op() == PutByVal || m_node->op() == PutByValDirect) {
+ HeapLocation heap;
+
+ Node* base = m_graph.varArgChild(m_node, 0).node();
+ Node* index = m_graph.varArgChild(m_node, 1).node();
+
+ ArrayMode mode = m_node->arrayMode();
+ switch (mode.type()) {
+ case Array::Int32:
+ if (!mode.isInBounds())
+ break;
+ heap = HeapLocation(
+ IndexedPropertyLoc, IndexedInt32Properties, base, index);
+ break;
+
+ case Array::Double:
+ if (!mode.isInBounds())
+ break;
+ heap = HeapLocation(
+ IndexedPropertyLoc, IndexedDoubleProperties, base, index);
+ break;
+
+ case Array::Contiguous:
+ if (!mode.isInBounds())
+ break;
+ heap = HeapLocation(
+ IndexedPropertyLoc, IndexedContiguousProperties, base, index);
+ break;
+
+ case Array::Int8Array:
+ case Array::Int16Array:
+ case Array::Int32Array:
+ case Array::Uint8Array:
+ case Array::Uint8ClampedArray:
+ case Array::Uint16Array:
+ case Array::Uint32Array:
+ case Array::Float32Array:
+ case Array::Float64Array:
+ if (!mode.isInBounds())
+ break;
+ heap = HeapLocation(
+ IndexedPropertyLoc, TypedArrayProperties, base, index);
+ break;
+
+ default:
+ break;
+ }
+
+ if (!!heap && m_maps.findReplacement(heap))
+ m_node->setOp(PutByValAlias);
+ }
+
+ clobberize(m_graph, m_node, *this);
}
}
+
+ return m_changed;
+ }
+
+ void read(AbstractHeap) { }
+
+ void write(AbstractHeap heap)
+ {
+ m_maps.write(heap);
}
- for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
- BasicBlock* block = m_graph.block(blockIndex);
- if (!block)
- continue;
+ void def(PureValue value)
+ {
+ Node* match = m_maps.addPure(value, m_node);
+ if (!match)
+ return;
+
+ m_node->replaceWith(match);
+ m_changed = true;
+ }
+
+ void def(HeapLocation location, Node* value)
+ {
+ Node* match = m_maps.addImpure(location, value);
+ if (!match)
+ return;
+
+ if (m_node->op() == GetLocal) {
+ // For uncaptured locals, usually the CPS rethreading phase does this. But it's OK
+ // for us to mess with locals - regardless of their capturedness - so long as:
+ //
+ // - We dethread the graph. Any changes we make may invalidate the assumptions of
+ // our CPS form, particularly if this GetLocal is linked to the variablesAtTail.
+ //
+ // - We don't introduce a Phantom for the child of the GetLocal. This wouldn't be
+ // totally wrong but it would pessimize the code. Just because there is a
+ // GetLocal doesn't mean that the child was live. Simply rerouting the all uses
+ // of this GetLocal will preserve the live-at-exit information just fine.
+ //
+ // We accomplish the latter by just clearing the child; then the Phantom that we
+ // introduce won't have children and so it will eventually just be deleted.
- for (unsigned i = block->size(); i--;) {
- Node* node = block->at(i);
- if (!node->containsMovHint())
- continue;
-
- ASSERT(node->op() != ZombieHint);
- node->child1()->mergeFlags(NodeRelevantToOSR);
+ m_node->child1() = Edge();
+ m_graph.dethread();
}
+
+ m_node->replaceWith(match);
+ m_changed = true;
+ }
+
+ private:
+ Graph& m_graph;
+
+ bool m_changed;
+ Node* m_node;
+
+ Maps m_maps;
+ };
+
+ BlockCSE<SmallMaps> m_smallBlock;
+ BlockCSE<LargeMaps> m_largeBlock;
+};
+
+class GlobalCSEPhase : public Phase {
+public:
+ GlobalCSEPhase(Graph& graph)
+ : Phase(graph, "global common subexpression elimination")
+ {
+ }
+
+ bool run()
+ {
+ ASSERT(m_graph.m_fixpointState == FixpointNotConverged);
+ ASSERT(m_graph.m_form == SSA);
+
+ m_graph.initializeNodeOwners();
+ m_graph.m_dominators.computeIfNecessary(m_graph);
+
+ m_graph.getBlocksInPreOrder(m_preOrder);
+
+ m_impureDataMap.resize(m_graph.numBlocks());
+
+ // First figure out what gets clobbered by blocks. Node that this uses the preOrder list
+ // for convenience only.
+ for (unsigned i = m_preOrder.size(); i--;) {
+ m_block = m_preOrder[i];
+ m_impureData = &m_impureDataMap[m_block->index];
+ for (unsigned nodeIndex = m_block->size(); nodeIndex--;)
+ addWrites(m_graph, m_block->at(nodeIndex), m_impureData->writes);
}
- if (m_graph.m_form == SSA) {
- Vector<BasicBlock*> depthFirst;
- m_graph.getBlocksInDepthFirstOrder(depthFirst);
- for (unsigned i = 0; i < depthFirst.size(); ++i)
- performBlockCSE(depthFirst[i]);
- } else {
- for (unsigned blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex)
- performBlockCSE(m_graph.block(blockIndex));
+ // Based on my experience doing this before, what follows might have to be made iterative.
+ // Right now it doesn't have to be iterative because everything is dominator-bsed. But when
+ // validation is enabled, we check if iterating would find new CSE opportunities.
+
+ bool changed = iterate();
+
+ // Iterating a second time should not find new CSE opportunities, unless we have a bug.
+ if (validationEnabled()) {
+ reset();
+ DFG_ASSERT(m_graph, nullptr, !iterate());
+ }
+
+ return changed;
+ }
+
+ void reset()
+ {
+ m_pureValues.clear();
+
+ for (unsigned i = m_impureDataMap.size(); i--;) {
+ m_impureDataMap[i].availableAtTail.clear();
+ m_impureDataMap[i].didVisit = false;
+ }
+ }
+
+ bool iterate()
+ {
+ if (verbose)
+ dataLog("Performing iteration.\n");
+
+ m_changed = false;
+ m_graph.clearReplacements();
+
+ for (unsigned i = 0; i < m_preOrder.size(); ++i) {
+ m_block = m_preOrder[i];
+ m_impureData = &m_impureDataMap[m_block->index];
+ m_writesSoFar.clear();
+
+ if (verbose)
+ dataLog("Processing block ", *m_block, ":\n");
+
+ for (unsigned nodeIndex = 0; nodeIndex < m_block->size(); ++nodeIndex) {
+ m_node = m_block->at(nodeIndex);
+ if (verbose)
+ dataLog(" Looking at node ", m_node, ":\n");
+
+ m_graph.performSubstitution(m_node);
+
+ if (m_node->op() == Identity) {
+ m_node->convertToCheck();
+ m_node->replacement = m_node->child1().node();
+ m_changed = true;
+ } else
+ clobberize(m_graph, m_node, *this);
+ }
+
+ m_impureData->didVisit = true;
}
return m_changed;
}
-
-private:
-
- unsigned endIndexForPureCSE()
- {
- unsigned result = m_lastSeen[m_currentNode->op()];
- if (result == UINT_MAX)
- result = 0;
- else
- result++;
- ASSERT(result <= m_indexInBlock);
- return result;
- }
- Node* pureCSE(Node* node)
+ void read(AbstractHeap) { }
+
+ void write(AbstractHeap heap)
{
- Edge child1 = node->child1().sanitized();
- Edge child2 = node->child2().sanitized();
- Edge child3 = node->child3().sanitized();
+ clobber(m_impureData->availableAtTail, heap);
+ m_writesSoFar.add(heap);
+ if (verbose)
+ dataLog(" Clobbered, new tail map: ", mapDump(m_impureData->availableAtTail), "\n");
+ }
+
+ void def(PureValue value)
+ {
+ // With pure values we do not have to worry about the possibility of some control flow path
+ // clobbering the value. So, we just search for all of the like values that have been
+ // computed. We pick one that is in a block that dominates ours. Note that this means that
+ // a PureValue will map to a list of nodes, since there may be many places in the control
+ // flow graph that compute a value but only one of them that dominates us. we may build up
+ // a large list of nodes that compute some value in the case of gnarly control flow. This
+ // is probably OK.
- for (unsigned i = endIndexForPureCSE(); i--;) {
- Node* otherNode = m_currentBlock->at(i);
- if (otherNode == child1 || otherNode == child2 || otherNode == child3)
- break;
-
- if (node->op() != otherNode->op())
- continue;
-
- Edge otherChild = otherNode->child1().sanitized();
- if (!otherChild)
- return otherNode;
- if (otherChild != child1)
- continue;
-
- otherChild = otherNode->child2().sanitized();
- if (!otherChild)
- return otherNode;
- if (otherChild != child2)
- continue;
-
- otherChild = otherNode->child3().sanitized();
- if (!otherChild)
- return otherNode;
- if (otherChild != child3)
- continue;
-
- return otherNode;
- }
- return 0;
- }
-
- Node* constantCSE(Node* node)
- {
- for (unsigned i = endIndexForPureCSE(); i--;) {
- Node* otherNode = m_currentBlock->at(i);
- if (otherNode->op() != node->op())
- continue;
-
- if (otherNode->constant() != node->constant())
- continue;
-
- return otherNode;
- }
- return 0;
- }
-
- Node* constantStoragePointerCSE(Node* node)
- {
- for (unsigned i = endIndexForPureCSE(); i--;) {
- Node* otherNode = m_currentBlock->at(i);
- if (otherNode->op() != ConstantStoragePointer)
- continue;
-
- if (otherNode->storagePointer() != node->storagePointer())
- continue;
-
- return otherNode;
- }
- return 0;
- }
-
- Node* getCalleeLoadElimination()
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- switch (node->op()) {
- case GetCallee:
- return node;
- default:
- break;
- }
- }
- return 0;
- }
-
- Node* getArrayLengthElimination(Node* array)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- switch (node->op()) {
- case GetArrayLength:
- if (node->child1() == array)
- return node;
- break;
-
- case PutByValDirect:
- case PutByVal:
- if (!m_graph.byValIsPure(node))
- return 0;
- if (node->arrayMode().mayStoreToHole())
- return 0;
- break;
-
- default:
- if (m_graph.clobbersWorld(node))
- return 0;
- }
- }
- return 0;
- }
-
- Node* globalVarLoadElimination(WriteBarrier<Unknown>* registerPointer)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- switch (node->op()) {
- case GetGlobalVar:
- if (node->registerPointer() == registerPointer)
- return node;
- break;
- case PutGlobalVar:
- if (node->registerPointer() == registerPointer)
- return node->child1().node();
- break;
- default:
- break;
- }
- if (m_graph.clobbersWorld(node))
- break;
- }
- return 0;
- }
-
- Node* scopedVarLoadElimination(Node* registers, int varNumber)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- switch (node->op()) {
- case GetClosureVar: {
- if (node->child1() == registers && node->varNumber() == varNumber)
- return node;
- break;
- }
- case PutClosureVar: {
- if (node->varNumber() != varNumber)
- break;
- if (node->child2() == registers)
- return node->child3().node();
- return 0;
- }
- case SetLocal: {
- VariableAccessData* variableAccessData = node->variableAccessData();
- if (variableAccessData->isCaptured()
- && variableAccessData->local() == static_cast<VirtualRegister>(varNumber))
- return 0;
- break;
- }
- default:
- break;
- }
- if (m_graph.clobbersWorld(node))
- break;
- }
- return 0;
- }
-
- bool varInjectionWatchpointElimination()
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node->op() == VarInjectionWatchpoint)
- return true;
- if (m_graph.clobbersWorld(node))
- break;
- }
- return false;
- }
-
- Node* getByValLoadElimination(Node* child1, Node* child2, ArrayMode arrayMode)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1 || node == child2)
- break;
-
- switch (node->op()) {
- case GetByVal:
- if (!m_graph.byValIsPure(node))
- return 0;
- if (node->child1() == child1
- && node->child2() == child2
- && node->arrayMode().type() == arrayMode.type())
- return node;
- break;
-
- case PutByValDirect:
- case PutByVal:
- case PutByValAlias: {
- if (!m_graph.byValIsPure(node))
- return 0;
- // Typed arrays
- if (arrayMode.typedArrayType() != NotTypedArray)
- return 0;
- if (m_graph.varArgChild(node, 0) == child1
- && m_graph.varArgChild(node, 1) == child2
- && node->arrayMode().type() == arrayMode.type())
- return m_graph.varArgChild(node, 2).node();
- // We must assume that the PutByVal will clobber the location we're getting from.
- // FIXME: We can do better; if we know that the PutByVal is accessing an array of a
- // different type than the GetByVal, then we know that they won't clobber each other.
- // ... except of course for typed arrays, where all typed arrays clobber all other
- // typed arrays! An Int32Array can alias a Float64Array for example, and so on.
- return 0;
- }
- default:
- if (m_graph.clobbersWorld(node))
- return 0;
- break;
- }
- }
- return 0;
- }
-
- bool checkFunctionElimination(FrozenValue* function, Node* child1)
- {
- for (unsigned i = endIndexForPureCSE(); i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1)
- break;
-
- if (node->op() == CheckFunction && node->child1() == child1 && node->function() == function)
- return true;
- }
- return false;
- }
-
- bool checkExecutableElimination(ExecutableBase* executable, Node* child1)
- {
- for (unsigned i = endIndexForPureCSE(); i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1)
- break;
-
- if (node->op() == CheckExecutable && node->child1() == child1 && node->executable() == executable)
- return true;
- }
- return false;
- }
-
- bool checkStructureElimination(const StructureSet& structureSet, Node* child1)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1)
- break;
-
- switch (node->op()) {
- case CheckStructure:
- if (node->child1() == child1
- && structureSet.isSupersetOf(node->structureSet()))
- return true;
- break;
-
- case PutStructure:
- if (node->child1() == child1
- && structureSet.contains(node->transition()->next))
- return true;
- if (structureSet.contains(node->transition()->previous))
- return false;
- break;
-
- case PutByOffset:
- // Setting a property cannot change the structure.
- break;
-
- case MultiPutByOffset:
- if (node->multiPutByOffsetData().writesStructures())
- return false;
- break;
-
- case PutByValDirect:
- case PutByVal:
- case PutByValAlias:
- if (m_graph.byValIsPure(node)) {
- // If PutByVal speculates that it's accessing an array with an
- // integer index, then it's impossible for it to cause a structure
- // change.
- break;
- }
- return false;
-
- case Arrayify:
- case ArrayifyToStructure:
- // We could check if the arrayification could affect our structures.
- // But that seems like it would take Effort.
- return false;
-
- default:
- if (m_graph.clobbersWorld(node))
- return false;
- break;
- }
- }
- return false;
- }
-
- bool structureTransitionWatchpointElimination(Structure* structure, Node* child1)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1)
- break;
-
- switch (node->op()) {
- case CheckStructure:
- if (node->child1() == child1
- && node->structureSet().isSubsetOf(StructureSet(structure)))
- return true;
- break;
-
- case PutStructure:
- ASSERT(node->transition()->previous != structure);
- break;
-
- case PutByOffset:
- // Setting a property cannot change the structure.
- break;
-
- case MultiPutByOffset:
- if (node->multiPutByOffsetData().writesStructures())
- return false;
- break;
-
- case PutByValDirect:
- case PutByVal:
- case PutByValAlias:
- if (m_graph.byValIsPure(node)) {
- // If PutByVal speculates that it's accessing an array with an
- // integer index, then it's impossible for it to cause a structure
- // change.
- break;
- }
- return false;
-
- case Arrayify:
- case ArrayifyToStructure:
- // We could check if the arrayification could affect our structures.
- // But that seems like it would take Effort.
- return false;
-
- default:
- if (m_graph.clobbersWorld(node))
- return false;
- break;
- }
- }
- return false;
- }
-
- Node* getByOffsetLoadElimination(unsigned identifierNumber, Node* base)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == base)
- break;
-
- switch (node->op()) {
- case GetByOffset:
- if (node->child2() == base
- && m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber == identifierNumber)
- return node;
- break;
-
- case MultiGetByOffset:
- if (node->child1() == base
- && node->multiGetByOffsetData().identifierNumber == identifierNumber)
- return node;
- break;
-
- case PutByOffset:
- if (m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber == identifierNumber) {
- if (node->child2() == base) // Must be same property storage.
- return node->child3().node();
- return 0;
- }
- break;
-
- case MultiPutByOffset:
- if (node->multiPutByOffsetData().identifierNumber == identifierNumber) {
- if (node->child1() == base)
- return node->child2().node();
- return 0;
- }
- break;
-
- case PutByValDirect:
- case PutByVal:
- case PutByValAlias:
- if (m_graph.byValIsPure(node)) {
- // If PutByVal speculates that it's accessing an array with an
- // integer index, then it's impossible for it to cause a structure
- // change.
- break;
- }
- return 0;
-
- default:
- if (m_graph.clobbersWorld(node))
- return 0;
- break;
- }
- }
- return 0;
- }
-
- Node* getGetterSetterByOffsetLoadElimination(unsigned identifierNumber, Node* base)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == base)
- break;
-
- switch (node->op()) {
- case GetGetterSetterByOffset:
- if (node->child2() == base
- && m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber == identifierNumber)
- return node;
- break;
-
- case PutByValDirect:
- case PutByVal:
- case PutByValAlias:
- if (m_graph.byValIsPure(node)) {
- // If PutByVal speculates that it's accessing an array with an
- // integer index, then it's impossible for it to cause a structure
- // change.
- break;
- }
- return 0;
-
- default:
- if (m_graph.clobbersWorld(node))
- return 0;
- break;
- }
- }
- return 0;
- }
-
- Node* getPropertyStorageLoadElimination(Node* child1)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1)
- break;
-
- switch (node->op()) {
- case GetButterfly:
- if (node->child1() == child1)
- return node;
- break;
-
- case AllocatePropertyStorage:
- case ReallocatePropertyStorage:
- // If we can cheaply prove this is a change to our object's storage, we
- // can optimize and use its result.
- if (node->child1() == child1)
- return node;
- // Otherwise, we currently can't prove that this doesn't change our object's
- // storage, so we conservatively assume that it may change the storage
- // pointer of any object, including ours.
- return 0;
-
- case PutByValDirect:
- case PutByVal:
- case PutByValAlias:
- if (m_graph.byValIsPure(node)) {
- // If PutByVal speculates that it's accessing an array with an
- // integer index, then it's impossible for it to cause a structure
- // change.
- break;
- }
- return 0;
-
- case Arrayify:
- case ArrayifyToStructure:
- // We could check if the arrayification could affect our butterfly.
- // But that seems like it would take Effort.
- return 0;
-
- case MultiPutByOffset:
- if (node->multiPutByOffsetData().reallocatesStorage())
- return 0;
- break;
-
- default:
- if (m_graph.clobbersWorld(node))
- return 0;
- break;
- }
- }
- return 0;
- }
-
- bool checkArrayElimination(Node* child1, ArrayMode arrayMode)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1)
- break;
-
- switch (node->op()) {
- case CheckArray:
- if (node->child1() == child1 && node->arrayMode() == arrayMode)
- return true;
- break;
-
- case Arrayify:
- case ArrayifyToStructure:
- // We could check if the arrayification could affect our array.
- // But that seems like it would take Effort.
- return false;
-
- default:
- if (m_graph.clobbersWorld(node))
- return false;
- break;
- }
- }
- return false;
- }
-
- Node* getIndexedPropertyStorageLoadElimination(Node* child1, ArrayMode arrayMode)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1)
- break;
-
- switch (node->op()) {
- case GetIndexedPropertyStorage: {
- if (node->child1() == child1 && node->arrayMode() == arrayMode)
- return node;
- break;
- }
-
- default:
- if (m_graph.clobbersWorld(node))
- return 0;
- break;
- }
- }
- return 0;
- }
-
- Node* getInternalFieldLoadElimination(NodeType op, Node* child1)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node == child1)
- break;
-
- if (node->op() == op && node->child1() == child1)
- return node;
-
- if (m_graph.clobbersWorld(node))
- return 0;
- }
- return 0;
- }
-
- Node* getMyScopeLoadElimination()
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- switch (node->op()) {
- case CreateActivation:
- // This may cause us to return a different scope.
- return 0;
- case GetMyScope:
- return node;
- default:
- break;
- }
- }
- return 0;
- }
-
- Node* getLocalLoadElimination(VirtualRegister local, Node*& relevantLocalOp, bool careAboutClobbering)
- {
- relevantLocalOp = 0;
-
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- switch (node->op()) {
- case GetLocal:
- if (node->local() == local) {
- relevantLocalOp = node;
- return node;
- }
- break;
-
- case GetLocalUnlinked:
- if (node->unlinkedLocal() == local) {
- relevantLocalOp = node;
- return node;
- }
- break;
-
- case SetLocal:
- if (node->local() == local) {
- relevantLocalOp = node;
- return node->child1().node();
- }
- break;
-
- case GetClosureVar:
- case PutClosureVar:
- if (static_cast<VirtualRegister>(node->varNumber()) == local)
- return 0;
- break;
-
- default:
- if (careAboutClobbering && m_graph.clobbersWorld(node))
- return 0;
- break;
- }
- }
- return 0;
- }
-
- Node* uncapturedSetLocalStoreElimination(VirtualRegister local)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- switch (node->op()) {
- case GetLocal:
- case Flush:
- if (node->local() == local)
- return nullptr;
- break;
-
- case GetLocalUnlinked:
- if (node->unlinkedLocal() == local)
- return nullptr;
- break;
-
- case SetLocal: {
- if (node->local() != local)
- break;
- return node;
- }
-
- case GetClosureVar:
- case PutClosureVar:
- if (static_cast<VirtualRegister>(node->varNumber()) == local)
- return nullptr;
- break;
-
- case GetMyScope:
- case SkipTopScope:
- if (node->origin.semantic.inlineCallFrame)
- break;
- if (m_graph.uncheckedActivationRegister() == local)
- return nullptr;
- break;
-
- case CheckArgumentsNotCreated:
- case GetMyArgumentsLength:
- case GetMyArgumentsLengthSafe:
- if (m_graph.uncheckedArgumentsRegisterFor(node->origin.semantic) == local)
- return nullptr;
- break;
-
- case GetMyArgumentByVal:
- case GetMyArgumentByValSafe:
- return nullptr;
-
- case GetByVal:
- // If this is accessing arguments then it's potentially accessing locals.
- if (node->arrayMode().type() == Array::Arguments)
- return nullptr;
- break;
-
- case CreateArguments:
- case TearOffActivation:
- case TearOffArguments:
- // If an activation is being torn off then it means that captured variables
- // are live. We could be clever here and check if the local qualifies as an
- // argument register. But that seems like it would buy us very little since
- // any kind of tear offs are rare to begin with.
- return nullptr;
-
- default:
- break;
- }
- if (m_graph.clobbersWorld(node))
- return nullptr;
- }
- return nullptr;
- }
-
- Node* capturedSetLocalStoreElimination(VirtualRegister local)
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- switch (node->op()) {
- case GetLocal:
- case Flush:
- if (node->local() == local)
- return nullptr;
- break;
-
- case GetLocalUnlinked:
- if (node->unlinkedLocal() == local)
- return nullptr;
- break;
-
- case SetLocal: {
- if (node->local() != local)
- break;
- return node;
- }
-
- case Phantom:
- case Check:
- case HardPhantom:
- case MovHint:
- case JSConstant:
- case DoubleConstant:
- case Int52Constant:
- break;
-
- default:
- return nullptr;
- }
- }
- return nullptr;
- }
-
- Node* setLocalStoreElimination(VariableAccessData* variableAccessData)
- {
- if (variableAccessData->isCaptured())
- return capturedSetLocalStoreElimination(variableAccessData->local());
- return uncapturedSetLocalStoreElimination(variableAccessData->local());
- }
-
- bool invalidationPointElimination()
- {
- for (unsigned i = m_indexInBlock; i--;) {
- Node* node = m_currentBlock->at(i);
- if (node->op() == InvalidationPoint)
- return true;
- if (writesOverlap(m_graph, node, Watchpoint_fire))
- break;
- }
- return false;
- }
-
- void eliminateIrrelevantPhantomChildren(Node* node)
- {
- ASSERT(node->op() == Phantom);
- for (unsigned i = 0; i < AdjacencyList::Size; ++i) {
- Edge edge = node->children.child(i);
- if (!edge)
- continue;
- if (edge.useKind() != UntypedUse)
- continue; // Keep the type check.
- if (edge->flags() & NodeRelevantToOSR)
- continue;
-
- node->children.removeEdge(i--);
- m_changed = true;
- }
- }
-
- bool setReplacement(Node* replacement)
- {
- if (!replacement)
- return false;
-
- if (!ASSERT_DISABLED
- && canonicalResultRepresentation(m_currentNode->result()) != canonicalResultRepresentation(replacement->result())) {
- startCrashing();
- dataLog("CSE attempting to replace a node with a mismatched result: ", m_currentNode, " with ", replacement, "\n");
- dataLog("\n");
- m_graph.dump();
- RELEASE_ASSERT_NOT_REACHED();
- }
-
- m_currentNode->convertToPhantom();
- eliminateIrrelevantPhantomChildren(m_currentNode);
-
- // At this point we will eliminate all references to this node.
- m_currentNode->misc.replacement = replacement;
-
- m_changed = true;
-
- return true;
- }
-
- void eliminate()
- {
- ASSERT(m_currentNode->mustGenerate());
- m_currentNode->convertToPhantom();
- eliminateIrrelevantPhantomChildren(m_currentNode);
-
- m_changed = true;
- }
-
- void eliminate(Node* node, NodeType phantomType = Phantom)
- {
- if (!node)
+ auto result = m_pureValues.add(value, Vector<Node*>());
+ if (result.isNewEntry) {
+ result.iterator->value.append(m_node);
return;
- ASSERT(node->mustGenerate());
- node->setOpAndDefaultFlags(phantomType);
- if (phantomType == Phantom)
- eliminateIrrelevantPhantomChildren(node);
+ }
- m_changed = true;
- }
-
- void performNodeCSE(Node* node)
- {
- m_graph.performSubstitution(node);
-
- switch (node->op()) {
-
- case Identity:
- setReplacement(node->child1().node());
- break;
-
- // Handle the pure nodes. These nodes never have any side-effects.
- case BitAnd:
- case BitOr:
- case BitXor:
- case BitRShift:
- case BitLShift:
- case BitURShift:
- case ArithAbs:
- case ArithMin:
- case ArithMax:
- case ArithSqrt:
- case ArithFRound:
- case ArithSin:
- case ArithCos:
- case StringCharAt:
- case StringCharCodeAt:
- case IsUndefined:
- case IsBoolean:
- case IsNumber:
- case IsString:
- case IsObject:
- case IsFunction:
- case LogicalNot:
- case SkipTopScope:
- case SkipScope:
- case GetClosureRegisters:
- case GetScope:
- case TypeOf:
- case CompareEqConstant:
- case ValueToInt32:
- case MakeRope:
- case DoubleRep:
- case ValueRep:
- case Int52Rep:
- case BooleanToNumber:
- setReplacement(pureCSE(node));
- break;
-
- case ArithAdd:
- case ArithSub:
- case ArithNegate:
- case ArithMul:
- case ArithDiv:
- case ArithMod:
- case UInt32ToNumber:
- case DoubleAsInt32: {
- Node* candidate = pureCSE(node);
- if (!candidate)
- break;
- if (!subsumes(candidate->arithMode(), node->arithMode())) {
- if (!subsumes(node->arithMode(), candidate->arithMode()))
- break;
- candidate->setArithMode(node->arithMode());
- }
- setReplacement(candidate);
- break;
- }
-
- case GetCallee:
- setReplacement(getCalleeLoadElimination());
- break;
-
- case GetLocal: {
- VariableAccessData* variableAccessData = node->variableAccessData();
- if (!variableAccessData->isCaptured())
- break;
- Node* relevantLocalOp;
- Node* possibleReplacement = getLocalLoadElimination(variableAccessData->local(), relevantLocalOp, variableAccessData->isCaptured());
- if (!relevantLocalOp)
- break;
- if (relevantLocalOp->op() != GetLocalUnlinked
- && relevantLocalOp->variableAccessData() != variableAccessData)
- break;
- Node* phi = node->child1().node();
- if (!setReplacement(possibleReplacement))
- break;
-
- m_graph.dethread();
-
- // If we replace a GetLocal with a GetLocalUnlinked, then turn the GetLocalUnlinked
- // into a GetLocal.
- if (relevantLocalOp->op() == GetLocalUnlinked)
- relevantLocalOp->convertToGetLocal(variableAccessData, phi);
-
- m_changed = true;
- break;
- }
-
- case GetLocalUnlinked: {
- Node* relevantLocalOpIgnored;
- setReplacement(getLocalLoadElimination(node->unlinkedLocal(), relevantLocalOpIgnored, true));
- break;
- }
-
- case JSConstant:
- case DoubleConstant:
- case Int52Constant:
- // This is strange, but necessary. Some phases will convert nodes to constants,
- // which may result in duplicated constants. We use CSE to clean this up.
- setReplacement(constantCSE(node));
- break;
-
- case ConstantStoragePointer:
- setReplacement(constantStoragePointerCSE(node));
- break;
-
- case GetArrayLength:
- setReplacement(getArrayLengthElimination(node->child1().node()));
- break;
-
- case GetMyScope:
- setReplacement(getMyScopeLoadElimination());
- break;
-
- // Handle nodes that are conditionally pure: these are pure, and can
- // be CSE'd, so long as the prediction is the one we want.
- case CompareLess:
- case CompareLessEq:
- case CompareGreater:
- case CompareGreaterEq:
- case CompareEq: {
- if (m_graph.isPredictedNumerical(node)) {
- Node* replacement = pureCSE(node);
- if (replacement && m_graph.isPredictedNumerical(replacement))
- setReplacement(replacement);
- }
- break;
- }
-
- // Finally handle heap accesses. These are not quite pure, but we can still
- // optimize them provided that some subtle conditions are met.
- case GetGlobalVar:
- setReplacement(globalVarLoadElimination(node->registerPointer()));
- break;
-
- case GetClosureVar: {
- setReplacement(scopedVarLoadElimination(node->child1().node(), node->varNumber()));
- break;
- }
-
- case VarInjectionWatchpoint:
- if (varInjectionWatchpointElimination())
- eliminate();
- break;
-
- case GetByVal:
- if (m_graph.byValIsPure(node))
- setReplacement(getByValLoadElimination(node->child1().node(), node->child2().node(), node->arrayMode()));
- break;
-
- case PutByValDirect:
- case PutByVal: {
- Edge child1 = m_graph.varArgChild(node, 0);
- Edge child2 = m_graph.varArgChild(node, 1);
- if (node->arrayMode().canCSEStorage()) {
- Node* replacement = getByValLoadElimination(child1.node(), child2.node(), node->arrayMode());
- if (!replacement)
- break;
- node->setOp(PutByValAlias);
- }
- break;
- }
-
- case CheckStructure:
- if (checkStructureElimination(node->structureSet(), node->child1().node()))
- eliminate();
- break;
-
- case CheckFunction:
- if (checkFunctionElimination(node->function(), node->child1().node()))
- eliminate();
- break;
-
- case CheckExecutable:
- if (checkExecutableElimination(node->executable(), node->child1().node()))
- eliminate();
- break;
-
- case CheckArray:
- if (checkArrayElimination(node->child1().node(), node->arrayMode()))
- eliminate();
- break;
-
- case GetIndexedPropertyStorage: {
- setReplacement(getIndexedPropertyStorageLoadElimination(node->child1().node(), node->arrayMode()));
- break;
- }
-
- case GetTypedArrayByteOffset:
- case GetGetter:
- case GetSetter: {
- setReplacement(getInternalFieldLoadElimination(node->op(), node->child1().node()));
- break;
- }
-
- case GetButterfly:
- setReplacement(getPropertyStorageLoadElimination(node->child1().node()));
- break;
-
- case GetByOffset:
- setReplacement(getByOffsetLoadElimination(m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber, node->child2().node()));
- break;
-
- case GetGetterSetterByOffset:
- setReplacement(getGetterSetterByOffsetLoadElimination(m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber, node->child2().node()));
- break;
-
- case MultiGetByOffset:
- setReplacement(getByOffsetLoadElimination(node->multiGetByOffsetData().identifierNumber, node->child1().node()));
- break;
-
- case InvalidationPoint:
- if (invalidationPointElimination())
- eliminate();
- break;
-
- case Phantom:
- // FIXME: we ought to remove Phantom's that have no children.
- // NB. It would be incorrect to do this for HardPhantom. In fact, the whole point
- // of HardPhantom is that we *don't* do this for HardPhantoms, since they signify
- // a more strict kind of liveness than the Phantom bytecode liveness.
- eliminateIrrelevantPhantomChildren(node);
- break;
-
- case Flush:
- // This is needed for arguments simplification to work. We need to eliminate the
- // redundancy between op_enter's undefined-all-the-things and the subsequent
- // op_init_lazy_reg.
-
- ASSERT(m_graph.m_form != SSA);
-
- if (Node* setLocal = setLocalStoreElimination(node->variableAccessData())) {
- node->convertToPhantom();
- Node* dataNode = setLocal->child1().node();
- ASSERT(dataNode->hasResult());
- node->child1() = dataNode->defaultEdge();
- m_graph.dethread();
+ for (unsigned i = result.iterator->value.size(); i--;) {
+ Node* candidate = result.iterator->value[i];
+ if (m_graph.m_dominators.dominates(candidate->owner, m_block)) {
+ m_node->replaceWith(candidate);
m_changed = true;
+ return;
}
- break;
-
- default:
- // do nothing.
- break;
}
- m_lastSeen[node->op()] = m_indexInBlock;
+ result.iterator->value.append(m_node);
}
- void performBlockCSE(BasicBlock* block)
+ Node* findReplacement(HeapLocation location)
{
- if (!block)
- return;
- if (!block->isReachable)
- return;
-
- m_currentBlock = block;
- for (unsigned i = 0; i < LastNodeType; ++i)
- m_lastSeen[i] = UINT_MAX;
-
- for (m_indexInBlock = 0; m_indexInBlock < block->size(); ++m_indexInBlock) {
- m_currentNode = block->at(m_indexInBlock);
- performNodeCSE(m_currentNode);
+ // At this instant, our "availableAtTail" reflects the set of things that are available in
+ // this block so far. We check this map to find block-local CSE opportunities before doing
+ // a global search.
+ Node* match = m_impureData->availableAtTail.get(location);
+ if (match) {
+ if (verbose)
+ dataLog(" Found local match: ", match, "\n");
+ return match;
}
+
+ // If it's not available at this point in the block, and at some prior point in the block
+ // we have clobbered this heap location, then there is no point in doing a global search.
+ if (m_writesSoFar.overlaps(location.heap())) {
+ if (verbose)
+ dataLog(" Not looking globally because of local clobber: ", m_writesSoFar, "\n");
+ return nullptr;
+ }
+
+ // This perfoms a backward search over the control flow graph to find some possible
+ // non-local def() that matches our heap location. Such a match is only valid if there does
+ // not exist any path from that def() to our block that contains a write() that overlaps
+ // our heap. This algorithm looks for both of these things (the matching def and the
+ // overlapping writes) in one backwards DFS pass.
+ //
+ // This starts by looking at the starting block's predecessors, and then it continues along
+ // their predecessors. As soon as this finds a possible def() - one that defines the heap
+ // location we want while dominating our starting block - it assumes that this one must be
+ // the match. It then lets the DFS over predecessors complete, but it doesn't add the
+ // def()'s predecessors; this ensures that any blocks we visit thereafter are on some path
+ // from the def() to us. As soon as the DFG finds a write() that overlaps the location's
+ // heap, it stops, assuming that there is no possible match. Note that the write() case may
+ // trigger before we find a def(), or after. Either way, the write() case causes this
+ // function to immediately return nullptr.
+ //
+ // If the write() is found before we find the def(), then we know that any def() we would
+ // find would have a path to us that trips over the write() and hence becomes invalid. This
+ // is just a direct outcome of us looking for a def() that dominates us. Given a block A
+ // that dominates block B - so that A is the one that would have the def() and B is our
+ // starting block - we know that any other block must either be on the path from A to B, or
+ // it must be on a path from the root to A, but not both. So, if we haven't found A yet but
+ // we already have found a block C that has a write(), then C must be on some path from A
+ // to B, which means that A's def() is invalid for our purposes. Hence, before we find the
+ // def(), stopping on write() is the right thing to do.
+ //
+ // Stopping on write() is also the right thing to do after we find the def(). After we find
+ // the def(), we don't add that block's predecessors to the search worklist. That means
+ // that henceforth the only blocks we will see in the search are blocks on the path from
+ // the def() to us. If any such block has a write() that clobbers our heap then we should
+ // give up.
+ //
+ // Hence this graph search algorithm ends up being deceptively simple: any overlapping
+ // write() causes us to immediately return nullptr, and a matching def() means that we just
+ // record it and neglect to visit its precessors.
+
+ Vector<BasicBlock*, 8> worklist;
+ Vector<BasicBlock*, 8> seenList;
+ BitVector seen;
+
+ for (unsigned i = m_block->predecessors.size(); i--;) {
+ BasicBlock* predecessor = m_block->predecessors[i];
+ if (!seen.get(predecessor->index)) {
+ worklist.append(predecessor);
+ seen.set(predecessor->index);
+ }
+ }
+
+ while (!worklist.isEmpty()) {
+ BasicBlock* block = worklist.takeLast();
+ seenList.append(block);
+
+ if (verbose)
+ dataLog(" Searching in block ", *block, "\n");
+ ImpureBlockData& data = m_impureDataMap[block->index];
+
+ // We require strict domination because this would only see things in our own block if
+ // they came *after* our position in the block. Clearly, while our block dominates
+ // itself, the things in the block after us don't dominate us.
+ if (m_graph.m_dominators.dominates(block, m_block) && block != m_block) {
+ if (verbose)
+ dataLog(" It strictly dominates.\n");
+ DFG_ASSERT(m_graph, m_node, data.didVisit);
+ DFG_ASSERT(m_graph, m_node, !match);
+ if (verbose)
+ dataLog(" Availability map: ", mapDump(data.availableAtTail), "\n");
+ match = data.availableAtTail.get(location);
+ if (verbose)
+ dataLog(" Availability: ", match, "\n");
+ if (match) {
+ // Don't examine the predecessors of a match. At this point we just want to
+ // establish that other blocks on the path from here to there don't clobber
+ // the location we're interested in.
+ continue;
+ }
+ }
+
+ if (verbose)
+ dataLog(" Dealing with write set ", data.writes, "\n");
+ if (data.writes.overlaps(location.heap())) {
+ if (verbose)
+ dataLog(" Clobbered.\n");
+ return nullptr;
+ }
+
+ for (unsigned i = block->predecessors.size(); i--;) {
+ BasicBlock* predecessor = block->predecessors[i];
+ if (!seen.get(predecessor->index)) {
+ worklist.append(predecessor);
+ seen.set(predecessor->index);
+ }
+ }
+ }
+
+ if (!match)
+ return nullptr;
+
+ // Cache the results for next time. We cache them both for this block and for all of our
+ // predecessors, since even though we've already visited our predecessors, our predecessors
+ // probably have successors other than us.
+ // FIXME: Consider caching failed searches as well, when match is null. It's not clear that
+ // the reduction in compile time would warrant the increase in complexity, though.
+ // https://bugs.webkit.org/show_bug.cgi?id=134876
+ for (BasicBlock* block : seenList)
+ m_impureDataMap[block->index].availableAtTail.add(location, match);
+ m_impureData->availableAtTail.add(location, match);
+
+ return match;
}
- BasicBlock* m_currentBlock;
- Node* m_currentNode;
- unsigned m_indexInBlock;
- std::array<unsigned, LastNodeType> m_lastSeen;
- bool m_changed; // Only tracks changes that have a substantive effect on other optimizations.
+ void def(HeapLocation location, Node* value)
+ {
+ if (verbose)
+ dataLog(" Got heap location def: ", location, " -> ", value, "\n");
+
+ Node* match = findReplacement(location);
+
+ if (verbose)
+ dataLog(" Got match: ", match, "\n");
+
+ if (!match) {
+ if (verbose)
+ dataLog(" Adding at-tail mapping: ", location, " -> ", value, "\n");
+ auto result = m_impureData->availableAtTail.add(location, value);
+ ASSERT_UNUSED(result, result.isNewEntry);
+ return;
+ }
+
+ m_node->replaceWith(match);
+ m_changed = true;
+ }
+
+ struct ImpureBlockData {
+ ImpureBlockData()
+ : didVisit(false)
+ {
+ }
+
+ ClobberSet writes;
+ ImpureMap availableAtTail;
+ bool didVisit;
+ };
+
+ Vector<BasicBlock*> m_preOrder;
+
+ PureMultiMap m_pureValues;
+ Vector<ImpureBlockData> m_impureDataMap;
+
+ BasicBlock* m_block;
+ Node* m_node;
+ ImpureBlockData* m_impureData;
+ ClobberSet m_writesSoFar;
+
+ bool m_changed;
};
-bool performCSE(Graph& graph)
+} // anonymous namespace
+
+bool performLocalCSE(Graph& graph)
{
- SamplingRegion samplingRegion("DFG CSE Phase");
- return runPhase<CSEPhase>(graph);
+ SamplingRegion samplingRegion("DFG LocalCSE Phase");
+ return runPhase<LocalCSEPhase>(graph);
+}
+
+bool performGlobalCSE(Graph& graph)
+{
+ SamplingRegion samplingRegion("DFG GlobalCSE Phase");
+ return runPhase<GlobalCSEPhase>(graph);
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)
-
-
diff --git a/Source/JavaScriptCore/dfg/DFGCSEPhase.h b/Source/JavaScriptCore/dfg/DFGCSEPhase.h
index 3e88479..562fd9b 100644
--- a/Source/JavaScriptCore/dfg/DFGCSEPhase.h
+++ b/Source/JavaScriptCore/dfg/DFGCSEPhase.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2011, 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -34,11 +34,20 @@
class Graph;
-// Block-local common subexpression elimination. This is an optional phase, but
-// it is rather profitable. It has fairly accurate heap modeling and will match
-// a wide range of subexpression similarities. It's known to produce big wins
-// on a few benchmarks, and is relatively cheap to run.
-bool performCSE(Graph&);
+// Block-local common subexpression elimination. It uses clobberize() for heap
+// modeling, which is quite precise. This phase is known to produce big wins on
+// a few benchmarks, and is relatively cheap to run.
+//
+// Note that this phase also gets rid of Identity nodes, which means that it's
+// currently not an optional phase. Basically, DFG IR doesn't have use-lists,
+// so there is no instantaneous replaceAllUsesWith operation. Instead, you turn
+// a node into an Identity and wait for CSE to clean it up.
+bool performLocalCSE(Graph&);
+
+// Same, but global. Only works for SSA. This will find common subexpressions
+// both in the same block and in any block that dominates the current block. It
+// has no limits on how far it will look for load-elimination opportunities.
+bool performGlobalCSE(Graph&);
} } // namespace JSC::DFG
diff --git a/Source/JavaScriptCore/dfg/DFGCapabilities.cpp b/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
index ab0fd25..4c8859b 100644
--- a/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
+++ b/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
@@ -45,6 +45,12 @@
&& FunctionWhitelist::ensureGlobalWhitelist().contains(codeBlock);
}
+bool isSupportedForInlining(CodeBlock* codeBlock)
+{
+ return !codeBlock->ownerExecutable()->needsActivation()
+ && codeBlock->ownerExecutable()->isInliningCandidate();
+}
+
bool mightCompileEval(CodeBlock* codeBlock)
{
return isSupported(codeBlock)
@@ -69,20 +75,17 @@
bool mightInlineFunctionForCall(CodeBlock* codeBlock)
{
return codeBlock->instructionCount() <= Options::maximumFunctionForCallInlineCandidateInstructionCount()
- && !codeBlock->ownerExecutable()->needsActivation()
- && codeBlock->ownerExecutable()->isInliningCandidate();
+ && isSupportedForInlining(codeBlock);
}
bool mightInlineFunctionForClosureCall(CodeBlock* codeBlock)
{
return codeBlock->instructionCount() <= Options::maximumFunctionForClosureCallInlineCandidateInstructionCount()
- && !codeBlock->ownerExecutable()->needsActivation()
- && codeBlock->ownerExecutable()->isInliningCandidate();
+ && isSupportedForInlining(codeBlock);
}
bool mightInlineFunctionForConstruct(CodeBlock* codeBlock)
{
return codeBlock->instructionCount() <= Options::maximumFunctionForConstructInlineCandidateInstructionCount()
- && !codeBlock->ownerExecutable()->needsActivation()
- && codeBlock->ownerExecutable()->isInliningCandidate();
+ && isSupportedForInlining(codeBlock);
}
inline void debugFail(CodeBlock* codeBlock, OpcodeID opcodeID, CapabilityLevel result)
diff --git a/Source/JavaScriptCore/dfg/DFGCapabilities.h b/Source/JavaScriptCore/dfg/DFGCapabilities.h
index b01f044..da0390d 100644
--- a/Source/JavaScriptCore/dfg/DFGCapabilities.h
+++ b/Source/JavaScriptCore/dfg/DFGCapabilities.h
@@ -39,6 +39,7 @@
// Fast check functions; if they return true it is still necessary to
// check opcodes.
bool isSupported(CodeBlock*);
+bool isSupportedForInlining(CodeBlock*);
bool mightCompileEval(CodeBlock*);
bool mightCompileProgram(CodeBlock*);
bool mightCompileFunctionForCall(CodeBlock*);
diff --git a/Source/JavaScriptCore/dfg/DFGClobberSet.cpp b/Source/JavaScriptCore/dfg/DFGClobberSet.cpp
index 997c4d2..d4630e3 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberSet.cpp
+++ b/Source/JavaScriptCore/dfg/DFGClobberSet.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -122,37 +122,38 @@
void addReads(Graph& graph, Node* node, ClobberSet& readSet)
{
ClobberSetAdd addRead(readSet);
- NoOpClobberize addWrite;
- clobberize(graph, node, addRead, addWrite);
+ NoOpClobberize noOp;
+ clobberize(graph, node, addRead, noOp, noOp);
}
void addWrites(Graph& graph, Node* node, ClobberSet& writeSet)
{
- NoOpClobberize addRead;
+ NoOpClobberize noOp;
ClobberSetAdd addWrite(writeSet);
- clobberize(graph, node, addRead, addWrite);
+ clobberize(graph, node, noOp, addWrite, noOp);
}
void addReadsAndWrites(Graph& graph, Node* node, ClobberSet& readSet, ClobberSet& writeSet)
{
ClobberSetAdd addRead(readSet);
ClobberSetAdd addWrite(writeSet);
- clobberize(graph, node, addRead, addWrite);
+ NoOpClobberize noOp;
+ clobberize(graph, node, addRead, addWrite, noOp);
}
bool readsOverlap(Graph& graph, Node* node, ClobberSet& readSet)
{
ClobberSetOverlaps addRead(readSet);
- NoOpClobberize addWrite;
- clobberize(graph, node, addRead, addWrite);
+ NoOpClobberize noOp;
+ clobberize(graph, node, addRead, noOp, noOp);
return addRead.result();
}
bool writesOverlap(Graph& graph, Node* node, ClobberSet& writeSet)
{
- NoOpClobberize addRead;
+ NoOpClobberize noOp;
ClobberSetOverlaps addWrite(writeSet);
- clobberize(graph, node, addRead, addWrite);
+ clobberize(graph, node, noOp, addWrite, noOp);
return addWrite.result();
}
diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.cpp b/Source/JavaScriptCore/dfg/DFGClobberize.cpp
index 18ca74e..ade3bb4 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberize.cpp
+++ b/Source/JavaScriptCore/dfg/DFGClobberize.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -34,17 +34,25 @@
bool doesWrites(Graph& graph, Node* node)
{
- NoOpClobberize addRead;
+ NoOpClobberize noOp;
CheckClobberize addWrite;
- clobberize(graph, node, addRead, addWrite);
+ clobberize(graph, node, noOp, addWrite, noOp);
return addWrite.result();
}
+bool accessesOverlap(Graph& graph, Node* node, AbstractHeap heap)
+{
+ NoOpClobberize noOp;
+ AbstractHeapOverlaps addAccess(heap);
+ clobberize(graph, node, addAccess, addAccess, noOp);
+ return addAccess.result();
+}
+
bool writesOverlap(Graph& graph, Node* node, AbstractHeap heap)
{
- NoOpClobberize addRead;
+ NoOpClobberize noOp;
AbstractHeapOverlaps addWrite(heap);
- clobberize(graph, node, addRead, addWrite);
+ clobberize(graph, node, noOp, addWrite, noOp);
return addWrite.result();
}
diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.h
index c85469a..51777bb 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberize.h
+++ b/Source/JavaScriptCore/dfg/DFGClobberize.h
@@ -31,11 +31,13 @@
#include "DFGAbstractHeap.h"
#include "DFGEdgeUsesStructure.h"
#include "DFGGraph.h"
+#include "DFGHeapLocation.h"
+#include "DFGPureValue.h"
namespace JSC { namespace DFG {
-template<typename ReadFunctor, typename WriteFunctor>
-void clobberize(Graph& graph, Node* node, ReadFunctor& read, WriteFunctor& write)
+template<typename ReadFunctor, typename WriteFunctor, typename DefFunctor>
+void clobberize(Graph& graph, Node* node, ReadFunctor& read, WriteFunctor& write, DefFunctor& def)
{
// Some notes:
//
@@ -69,6 +71,29 @@
// prior to type inference - though they probably could be if we did some
// small hacking.
+ // While read() and write() are fairly self-explanatory - they track what sorts of things the
+ // node may read or write - the def() functor is more tricky. It tells you the heap locations
+ // (not just abstract heaps) that are defined by a node. A heap location comprises an abstract
+ // heap, some nodes, and a LocationKind. Briefly, a location defined by a node is a location
+ // whose value can be deduced from looking at the node itself. The locations returned must obey
+ // the following properties:
+ //
+ // - If someone wants to CSE a load from the heap, then a HeapLocation object should be
+ // sufficient to find a single matching node.
+ //
+ // - The abstract heap is the only abstract heap that could be clobbered to invalidate any such
+ // CSE attempt. I.e. if clobberize() reports that on every path between some node and a node
+ // that defines a HeapLocation that it wanted, there were no writes to any abstract heap that
+ // overlap the location's heap, then we have a sound match. Effectively, the semantics of
+ // write() and def() are intertwined such that for them to be sound they must agree on what
+ // is CSEable.
+ //
+ // read(), write(), and def() for heap locations is enough to do GCSE on effectful things. To
+ // keep things simple, this code will also def() pure things. def() must be overloaded to also
+ // accept PureValue. This way, a client of clobberize() can implement GCSE entirely using the
+ // information that clobberize() passes to write() and def(). Other clients of clobberize() can
+ // just ignore def() by using a NoOpClobberize functor.
+
if (edgesUseStructure(graph, node))
read(JSCell_structureID);
@@ -76,23 +101,23 @@
case JSConstant:
case DoubleConstant:
case Int52Constant:
+ def(PureValue(node, node->constant()));
+ return;
+
case Identity:
case Phantom:
case HardPhantom:
+ case Check:
+ case ExtractOSREntryLocal:
+ return;
+
case BitAnd:
case BitOr:
case BitXor:
case BitLShift:
case BitRShift:
case BitURShift:
- case ValueToInt32:
- case ArithAdd:
- case ArithSub:
- case ArithNegate:
- case ArithMul:
case ArithIMul:
- case ArithDiv:
- case ArithMod:
case ArithAbs:
case ArithMin:
case ArithMax:
@@ -102,7 +127,6 @@
case ArithCos:
case GetScope:
case SkipScope:
- case CheckFunction:
case StringCharCodeAt:
case StringFromCharCode:
case CompareEqConstant:
@@ -112,25 +136,44 @@
case IsNumber:
case IsString:
case LogicalNot:
- case ExtractOSREntryLocal:
case CheckInBounds:
- case ConstantStoragePointer:
- case UInt32ToNumber:
- case DoubleAsInt32:
- case Check:
case DoubleRep:
case ValueRep:
case Int52Rep:
case BooleanToNumber:
case FiatInt52:
case MakeRope:
+ case ValueToInt32:
+ def(PureValue(node));
return;
+ case ArithAdd:
+ case ArithSub:
+ case ArithNegate:
+ case ArithMul:
+ case ArithDiv:
+ case ArithMod:
+ case DoubleAsInt32:
+ case UInt32ToNumber:
+ def(PureValue(node, node->arithMode()));
+ return;
+
+ case CheckFunction:
+ def(PureValue(CheckFunction, AdjacencyList(AdjacencyList::Fixed, node->child1()), node->function()));
+ return;
+
+ case CheckExecutable:
+ def(PureValue(node, node->executable()));
+ return;
+
+ case ConstantStoragePointer:
+ def(PureValue(node, node->storagePointer()));
+ return;
+
case MovHint:
case ZombieHint:
case Upsilon:
case Phi:
- case Flush:
case PhantomLocal:
case SetArgument:
case PhantomArguments:
@@ -145,7 +188,6 @@
case CheckTierUpAtReturn:
case CheckTierUpAndOSREnter:
case LoopHint:
- case InvalidationPoint:
case Breakpoint:
case ProfileWillCall:
case ProfileDidCall:
@@ -154,6 +196,16 @@
write(SideState);
return;
+ case InvalidationPoint:
+ write(SideState);
+ def(HeapLocation(InvalidationPointLoc, Watchpoint_fire), node);
+ return;
+
+ case Flush:
+ read(AbstractHeap(Variables, node->local()));
+ write(SideState);
+ return;
+
case VariableWatchpoint:
case TypedArrayWatchpoint:
read(Watchpoint_fire);
@@ -166,13 +218,20 @@
return;
case CreateActivation:
- case CreateArguments:
read(HeapObjectCount);
write(HeapObjectCount);
write(SideState);
write(Watchpoint_fire);
return;
+ case CreateArguments:
+ read(Variables);
+ read(HeapObjectCount);
+ write(HeapObjectCount);
+ write(SideState);
+ write(Watchpoint_fire);
+ return;
+
case FunctionReentryWatchpoint:
read(Watchpoint_fire);
return;
@@ -185,13 +244,30 @@
return;
case VarInjectionWatchpoint:
- case AllocationProfileWatchpoint:
- case IsObject:
- case IsFunction:
- case TypeOf:
read(MiscFields);
+ def(HeapLocation(VarInjectionWatchpointLoc, MiscFields), node);
+ return;
+
+ case AllocationProfileWatchpoint:
+ read(MiscFields);
+ def(HeapLocation(AllocationProfileWatchpointLoc, MiscFields), node);
return;
+ case IsObject:
+ read(MiscFields);
+ def(HeapLocation(IsObjectLoc, MiscFields, node->child1()), node);
+ return;
+
+ case IsFunction:
+ read(MiscFields);
+ def(HeapLocation(IsFunctionLoc, MiscFields, node->child1()), node);
+ return;
+
+ case TypeOf:
+ read(MiscFields);
+ def(HeapLocation(TypeOfLoc, MiscFields, node->child1()), node);
+ return;
+
case GetById:
case GetByIdFlush:
case PutById:
@@ -214,27 +290,33 @@
case GetGetter:
read(GetterSetter_getter);
+ def(HeapLocation(GetterLoc, GetterSetter_getter, node->child1()), node);
return;
case GetSetter:
read(GetterSetter_setter);
+ def(HeapLocation(SetterLoc, GetterSetter_setter, node->child1()), node);
return;
case GetCallee:
read(AbstractHeap(Variables, JSStack::Callee));
+ def(HeapLocation(VariableLoc, AbstractHeap(Variables, JSStack::Callee)), node);
return;
case GetLocal:
case GetArgument:
read(AbstractHeap(Variables, node->local()));
+ def(HeapLocation(VariableLoc, AbstractHeap(Variables, node->local())), node);
return;
case SetLocal:
write(AbstractHeap(Variables, node->local()));
+ def(HeapLocation(VariableLoc, AbstractHeap(Variables, node->local())), node->child1().node());
return;
case GetLocalUnlinked:
read(AbstractHeap(Variables, node->unlinkedLocal()));
+ def(HeapLocation(VariableLoc, AbstractHeap(Variables, node->unlinkedLocal())), node);
return;
case GetByVal: {
@@ -264,18 +346,20 @@
return;
}
// This appears to read nothing because it's only reading immutable data.
+ def(PureValue(node, mode.asWord()));
return;
case Array::Arguments:
read(Arguments_registers);
read(Variables);
+ def(HeapLocation(IndexedPropertyLoc, Variables, node->child1(), node->child2()), node);
return;
case Array::Int32:
if (mode.isInBounds()) {
read(Butterfly_publicLength);
- read(Butterfly_vectorLength);
read(IndexedInt32Properties);
+ def(HeapLocation(IndexedPropertyLoc, IndexedInt32Properties, node->child1(), node->child2()), node);
return;
}
read(World);
@@ -285,8 +369,8 @@
case Array::Double:
if (mode.isInBounds()) {
read(Butterfly_publicLength);
- read(Butterfly_vectorLength);
read(IndexedDoubleProperties);
+ def(HeapLocation(IndexedPropertyLoc, IndexedDoubleProperties, node->child1(), node->child2()), node);
return;
}
read(World);
@@ -296,8 +380,8 @@
case Array::Contiguous:
if (mode.isInBounds()) {
read(Butterfly_publicLength);
- read(Butterfly_vectorLength);
read(IndexedContiguousProperties);
+ def(HeapLocation(IndexedPropertyLoc, IndexedContiguousProperties, node->child1(), node->child2()), node);
return;
}
read(World);
@@ -306,7 +390,11 @@
case Array::ArrayStorage:
case Array::SlowPutArrayStorage:
- // Give up on life for now.
+ if (mode.isInBounds()) {
+ read(Butterfly_vectorLength);
+ read(IndexedArrayStorageProperties);
+ return;
+ }
read(World);
write(World);
return;
@@ -321,8 +409,8 @@
case Array::Float32Array:
case Array::Float64Array:
read(TypedArrayProperties);
- read(JSArrayBufferView_vector);
- read(JSArrayBufferView_length);
+ read(MiscFields);
+ def(HeapLocation(IndexedPropertyLoc, TypedArrayProperties, node->child1(), node->child2()), node);
return;
}
RELEASE_ASSERT_NOT_REACHED();
@@ -333,6 +421,9 @@
case PutByVal:
case PutByValAlias: {
ArrayMode mode = node->arrayMode();
+ Node* base = graph.varArgChild(node, 0).node();
+ Node* index = graph.varArgChild(node, 1).node();
+ Node* value = graph.varArgChild(node, 2).node();
switch (mode.modeForPut().type()) {
case Array::SelectUsingPredictions:
case Array::Unprofiled:
@@ -354,9 +445,9 @@
case Array::Arguments:
read(Arguments_registers);
- read(Arguments_numArguments);
- read(Arguments_slowArguments);
+ read(MiscFields);
write(Variables);
+ def(HeapLocation(IndexedPropertyLoc, Variables, base, index), value);
return;
case Array::Int32:
@@ -369,6 +460,9 @@
read(Butterfly_vectorLength);
read(IndexedInt32Properties);
write(IndexedInt32Properties);
+ if (node->arrayMode().mayStoreToHole())
+ write(Butterfly_publicLength);
+ def(HeapLocation(IndexedPropertyLoc, IndexedInt32Properties, base, index), value);
return;
case Array::Double:
@@ -381,6 +475,9 @@
read(Butterfly_vectorLength);
read(IndexedDoubleProperties);
write(IndexedDoubleProperties);
+ if (node->arrayMode().mayStoreToHole())
+ write(Butterfly_publicLength);
+ def(HeapLocation(IndexedPropertyLoc, IndexedDoubleProperties, base, index), value);
return;
case Array::Contiguous:
@@ -393,6 +490,9 @@
read(Butterfly_vectorLength);
read(IndexedContiguousProperties);
write(IndexedContiguousProperties);
+ if (node->arrayMode().mayStoreToHole())
+ write(Butterfly_publicLength);
+ def(HeapLocation(IndexedPropertyLoc, IndexedContiguousProperties, base, index), value);
return;
case Array::ArrayStorage:
@@ -411,9 +511,10 @@
case Array::Uint32Array:
case Array::Float32Array:
case Array::Float64Array:
- read(JSArrayBufferView_vector);
- read(JSArrayBufferView_length);
+ read(MiscFields);
write(TypedArrayProperties);
+ // FIXME: We can't def() anything here because these operations truncate their inputs.
+ // https://bugs.webkit.org/show_bug.cgi?id=134737
return;
}
RELEASE_ASSERT_NOT_REACHED();
@@ -421,7 +522,6 @@
}
case CheckStructure:
- case InstanceOf:
read(JSCell_structureID);
return;
@@ -433,12 +533,14 @@
case CheckHasInstance:
read(JSCell_typeInfoFlags);
+ def(HeapLocation(CheckHasInstanceLoc, JSCell_typeInfoFlags, node->child1()), node);
return;
- case CheckExecutable:
- read(JSFunction_executable);
+ case InstanceOf:
+ read(JSCell_structureID);
+ def(HeapLocation(InstanceOfLoc, JSCell_structureID, node->child1(), node->child2()), node);
return;
-
+
case PutStructure:
write(JSCell_structureID);
write(JSCell_typeInfoType);
@@ -448,15 +550,18 @@
case AllocatePropertyStorage:
write(JSObject_butterfly);
+ def(HeapLocation(ButterflyLoc, JSObject_butterfly, node->child1()), node);
return;
case ReallocatePropertyStorage:
read(JSObject_butterfly);
write(JSObject_butterfly);
+ def(HeapLocation(ButterflyLoc, JSObject_butterfly, node->child1()), node);
return;
case GetButterfly:
read(JSObject_butterfly);
+ def(HeapLocation(ButterflyLoc, JSObject_butterfly, node->child1()), node);
return;
case Arrayify:
@@ -471,42 +576,59 @@
return;
case GetIndexedPropertyStorage:
- if (node->arrayMode().type() == Array::String)
+ if (node->arrayMode().type() == Array::String) {
+ def(PureValue(node, node->arrayMode().asWord()));
return;
- read(JSArrayBufferView_vector);
+ }
+ read(MiscFields);
+ def(HeapLocation(IndexedPropertyStorageLoc, MiscFields, node->child1()), node);
return;
case GetTypedArrayByteOffset:
- read(JSArrayBufferView_vector);
- read(JSArrayBufferView_mode);
- read(Butterfly_arrayBuffer);
- read(ArrayBuffer_data);
+ read(MiscFields);
+ def(HeapLocation(TypedArrayByteOffsetLoc, MiscFields, node->child1()), node);
return;
case GetByOffset:
- case GetGetterSetterByOffset:
- read(AbstractHeap(NamedProperties, graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber));
+ case GetGetterSetterByOffset: {
+ unsigned identifierNumber =
+ graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber;
+ AbstractHeap heap(NamedProperties, identifierNumber);
+ read(heap);
+ def(HeapLocation(NamedPropertyLoc, heap, node->child2()), node);
return;
+ }
- case MultiGetByOffset:
+ case MultiGetByOffset: {
read(JSCell_structureID);
read(JSObject_butterfly);
- read(AbstractHeap(NamedProperties, node->multiGetByOffsetData().identifierNumber));
+ AbstractHeap heap(NamedProperties, node->multiGetByOffsetData().identifierNumber);
+ read(heap);
+ def(HeapLocation(NamedPropertyLoc, heap, node->child1()), node);
return;
+ }
- case MultiPutByOffset:
+ case MultiPutByOffset: {
read(JSCell_structureID);
read(JSObject_butterfly);
- write(AbstractHeap(NamedProperties, node->multiPutByOffsetData().identifierNumber));
+ AbstractHeap heap(NamedProperties, node->multiPutByOffsetData().identifierNumber);
+ write(heap);
if (node->multiPutByOffsetData().writesStructures())
write(JSCell_structureID);
if (node->multiPutByOffsetData().reallocatesStorage())
write(JSObject_butterfly);
+ def(HeapLocation(NamedPropertyLoc, heap, node->child1()), node->child2().node());
return;
+ }
- case PutByOffset:
- write(AbstractHeap(NamedProperties, graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber));
+ case PutByOffset: {
+ unsigned identifierNumber =
+ graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber;
+ AbstractHeap heap(NamedProperties, identifierNumber);
+ write(heap);
+ def(HeapLocation(NamedPropertyLoc, heap, node->child2()), node->child3().node());
return;
+ }
case GetArrayLength: {
ArrayMode mode = node->arrayMode();
@@ -517,78 +639,90 @@
case Array::ArrayStorage:
case Array::SlowPutArrayStorage:
read(Butterfly_publicLength);
+ def(HeapLocation(ArrayLengthLoc, Butterfly_publicLength, node->child1()), node);
return;
case Array::String:
+ def(PureValue(node, mode.asWord()));
return;
case Array::Arguments:
- read(Arguments_overrideLength);
- read(Arguments_numArguments);
+ read(MiscFields);
+ def(HeapLocation(ArrayLengthLoc, MiscFields, node->child1()), node);
return;
default:
- read(JSArrayBufferView_length);
+ ASSERT(mode.typedArrayType() != NotTypedArray);
+ read(MiscFields);
+ def(HeapLocation(ArrayLengthLoc, MiscFields, node->child1()), node);
return;
}
}
case GetMyScope:
- read(AbstractHeap(Variables, JSStack::ScopeChain));
+ if (graph.m_codeBlock->needsActivation()) {
+ read(AbstractHeap(Variables, JSStack::ScopeChain));
+ def(HeapLocation(VariableLoc, AbstractHeap(Variables, JSStack::ScopeChain)), node);
+ } else
+ def(PureValue(node));
return;
case SkipTopScope:
read(AbstractHeap(Variables, graph.activationRegister()));
+ def(HeapLocation(SkipTopScopeLoc, AbstractHeap(Variables, graph.activationRegister()), node->child1()), node);
return;
case GetClosureRegisters:
read(JSVariableObject_registers);
+ def(HeapLocation(ClosureRegistersLoc, JSVariableObject_registers, node->child1()), node);
return;
-
+
case GetClosureVar:
read(AbstractHeap(Variables, node->varNumber()));
+ def(HeapLocation(ClosureVariableLoc, AbstractHeap(Variables, node->varNumber()), node->child1()), node);
return;
case PutClosureVar:
write(AbstractHeap(Variables, node->varNumber()));
+ def(HeapLocation(ClosureVariableLoc, AbstractHeap(Variables, node->varNumber()), node->child2()), node->child3().node());
return;
case GetGlobalVar:
read(AbstractHeap(Absolute, node->registerPointer()));
+ def(HeapLocation(GlobalVariableLoc, AbstractHeap(Absolute, node->registerPointer())), node);
return;
case PutGlobalVar:
write(AbstractHeap(Absolute, node->registerPointer()));
+ def(HeapLocation(GlobalVariableLoc, AbstractHeap(Absolute, node->registerPointer())), node->child1().node());
return;
- case NewObject:
case NewArray:
case NewArrayWithSize:
case NewArrayBuffer:
+ case NewTypedArray:
+ // FIXME: Enable CSE for these nodes. We can't do this right now because there is no way
+ // for us to claim an index node and a value node. We could make this work if we lowered
+ // these nodes or if we had a more flexible way of def()'ing.
+ // https://bugs.webkit.org/show_bug.cgi?id=134737
+ read(HeapObjectCount);
+ write(HeapObjectCount);
+ return;
+
+ case NewObject:
case NewRegexp:
case NewStringObject:
+ read(HeapObjectCount);
+ write(HeapObjectCount);
+ return;
+
case NewFunctionNoCheck:
case NewFunction:
case NewFunctionExpression:
read(HeapObjectCount);
write(HeapObjectCount);
return;
-
- case NewTypedArray:
- read(HeapObjectCount);
- write(HeapObjectCount);
- switch (node->child1().useKind()) {
- case Int32Use:
- return;
- case UntypedUse:
- read(World);
- write(World);
- return;
- default:
- RELEASE_ASSERT_NOT_REACHED();
- return;
- }
-
+
case RegExpExec:
case RegExpTest:
read(RegExpState);
@@ -601,6 +735,7 @@
write(World);
return;
}
+ def(PureValue(node));
return;
case CompareEq:
@@ -608,8 +743,10 @@
case CompareLessEq:
case CompareGreater:
case CompareGreaterEq:
- if (!node->isBinaryUseKind(UntypedUse))
+ if (!node->isBinaryUseKind(UntypedUse)) {
+ def(PureValue(node));
return;
+ }
read(World);
write(World);
return;
@@ -618,6 +755,8 @@
switch (node->child1().useKind()) {
case StringObjectUse:
case StringOrStringObjectUse:
+ // These don't def a pure value, unfortunately. I'll avoid load-eliminating these for
+ // now.
return;
case CellUse:
@@ -632,20 +771,28 @@
}
case TearOffActivation:
+ read(Variables);
write(JSVariableObject_registers);
return;
case TearOffArguments:
+ read(Variables);
write(Arguments_registers);
return;
case GetMyArgumentsLength:
read(AbstractHeap(Variables, graph.argumentsRegisterFor(node->origin.semantic)));
read(AbstractHeap(Variables, JSStack::ArgumentCount));
+ // FIXME: We could def() this by specifying the code origin as a kind of m_info, like we
+ // have for PureValue.
+ // https://bugs.webkit.org/show_bug.cgi?id=134797
return;
case GetMyArgumentByVal:
read(Variables);
+ // FIXME: We could def() this by specifying the code origin as a kind of m_info, like we
+ // have for PureValue.
+ // https://bugs.webkit.org/show_bug.cgi?id=134797
return;
case CheckArgumentsNotCreated:
@@ -675,7 +822,8 @@
class NoOpClobberize {
public:
NoOpClobberize() { }
- void operator()(AbstractHeap) { }
+ template<typename... T>
+ void operator()(T...) { }
};
class CheckClobberize {
@@ -685,7 +833,8 @@
{
}
- void operator()(AbstractHeap) { m_result = true; }
+ template<typename... T>
+ void operator()(T...) { m_result = true; }
bool result() const { return m_result; }
@@ -717,8 +866,75 @@
bool m_result;
};
+bool accessesOverlap(Graph&, Node*, AbstractHeap);
bool writesOverlap(Graph&, Node*, AbstractHeap);
+// We would have used bind() for these, but because of the overlaoding that we are doing,
+// it's quite a bit of clearer to just write this out the traditional way.
+
+template<typename T>
+class ReadMethodClobberize {
+public:
+ ReadMethodClobberize(T& value)
+ : m_value(value)
+ {
+ }
+
+ void operator()(AbstractHeap heap)
+ {
+ m_value.read(heap);
+ }
+private:
+ T& m_value;
+};
+
+template<typename T>
+class WriteMethodClobberize {
+public:
+ WriteMethodClobberize(T& value)
+ : m_value(value)
+ {
+ }
+
+ void operator()(AbstractHeap heap)
+ {
+ m_value.write(heap);
+ }
+private:
+ T& m_value;
+};
+
+template<typename T>
+class DefMethodClobberize {
+public:
+ DefMethodClobberize(T& value)
+ : m_value(value)
+ {
+ }
+
+ void operator()(PureValue value)
+ {
+ m_value.def(value);
+ }
+
+ void operator()(HeapLocation location, Node* node)
+ {
+ m_value.def(location, node);
+ }
+
+private:
+ T& m_value;
+};
+
+template<typename Adaptor>
+void clobberize(Graph& graph, Node* node, Adaptor& adaptor)
+{
+ ReadMethodClobberize<Adaptor> read(adaptor);
+ WriteMethodClobberize<Adaptor> write(adaptor);
+ DefMethodClobberize<Adaptor> def(adaptor);
+ clobberize(graph, node, read, write, def);
+}
+
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)
diff --git a/Source/JavaScriptCore/dfg/DFGCommonData.h b/Source/JavaScriptCore/dfg/DFGCommonData.h
index af5c38d..593ea95 100644
--- a/Source/JavaScriptCore/dfg/DFGCommonData.h
+++ b/Source/JavaScriptCore/dfg/DFGCommonData.h
@@ -32,7 +32,6 @@
#include "DFGJumpReplacement.h"
#include "InlineCallFrameSet.h"
#include "JSCell.h"
-#include "ProfiledCodeBlockJettisoningWatchpoint.h"
#include "ProfilerCompilation.h"
#include "SymbolTable.h"
#include <wtf/Bag.h>
@@ -95,8 +94,8 @@
Vector<Identifier> dfgIdentifiers;
Vector<WeakReferenceTransition> transitions;
Vector<WriteBarrier<JSCell>> weakReferences;
+ Vector<WriteBarrier<Structure>> weakStructureReferences;
SegmentedVector<CodeBlockJettisoningWatchpoint, 1, 0> watchpoints;
- SegmentedVector<ProfiledCodeBlockJettisoningWatchpoint, 1, 0> profiledWatchpoints;
Vector<JumpReplacement> jumpReplacements;
RefPtr<Profiler::Compilation> compilation;
diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
index 878ee16..f018e18 100644
--- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
@@ -414,8 +414,13 @@
addBaseCheck(indexInBlock, node, baseValue, variant.structureSet());
- if (variant.specificValue()) {
- m_graph.convertToConstant(node, m_graph.freeze(variant.specificValue()));
+ JSValue baseForLoad;
+ if (variant.alternateBase())
+ baseForLoad = variant.alternateBase();
+ else
+ baseForLoad = baseValue.m_value;
+ if (JSValue value = m_graph.tryGetConstantProperty(baseForLoad, variant.baseStructure(), variant.offset())) {
+ m_graph.convertToConstant(node, m_graph.freeze(value));
return;
}
diff --git a/Source/JavaScriptCore/dfg/DFGDCEPhase.cpp b/Source/JavaScriptCore/dfg/DFGDCEPhase.cpp
index e8cf933..fd4740d 100644
--- a/Source/JavaScriptCore/dfg/DFGDCEPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDCEPhase.cpp
@@ -107,7 +107,7 @@
if (m_graph.m_form == SSA) {
Vector<BasicBlock*> depthFirst;
- m_graph.getBlocksInDepthFirstOrder(depthFirst);
+ m_graph.getBlocksInPreOrder(depthFirst);
for (unsigned i = 0; i < depthFirst.size(); ++i)
fixupBlock(depthFirst[i]);
} else {
@@ -193,9 +193,9 @@
switch (node->op()) {
case MovHint: {
- // Check if the child is dead. MovHint's child would only be a Phantom
- // if we had just killed it.
- if (node->child1()->op() == Phantom) {
+ // Check if the child is dead. MovHint's child would only be a Phantom or
+ // Check if we had just killed it.
+ if (node->child1()->op() == Phantom || node->child1()->op() == Check) {
node->setOpAndDefaultFlags(ZombieHint);
node->child1() = Edge();
break;
@@ -220,7 +220,7 @@
m_insertionSet.insertNode(indexInBlock, SpecNone, Phantom, node->origin, edge);
}
- node->convertToPhantomUnchecked();
+ node->convertToPhantom();
node->children.reset();
node->setRefCount(1);
break;
diff --git a/Source/JavaScriptCore/dfg/DFGDesiredWeakReferences.cpp b/Source/JavaScriptCore/dfg/DFGDesiredWeakReferences.cpp
index 1ee02ba..6c6adf4 100644
--- a/Source/JavaScriptCore/dfg/DFGDesiredWeakReferences.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDesiredWeakReferences.cpp
@@ -57,7 +57,13 @@
{
for (unsigned i = 0; i < m_references.size(); i++) {
JSCell* target = m_references[i];
- common->weakReferences.append(WriteBarrier<JSCell>(vm, m_codeBlock->ownerExecutable(), target));
+ if (Structure* structure = jsDynamicCast<Structure*>(target)) {
+ common->weakStructureReferences.append(
+ WriteBarrier<Structure>(vm, m_codeBlock->ownerExecutable(), structure));
+ } else {
+ common->weakReferences.append(
+ WriteBarrier<JSCell>(vm, m_codeBlock->ownerExecutable(), target));
+ }
}
}
diff --git a/Source/JavaScriptCore/dfg/DFGEdgeDominates.h b/Source/JavaScriptCore/dfg/DFGEdgeDominates.h
index 0af2e98..d95c79d 100644
--- a/Source/JavaScriptCore/dfg/DFGEdgeDominates.h
+++ b/Source/JavaScriptCore/dfg/DFGEdgeDominates.h
@@ -45,10 +45,10 @@
void operator()(Node*, Edge edge)
{
- bool result = m_graph.m_dominators.dominates(edge.node()->misc.owner, m_block);
+ bool result = m_graph.m_dominators.dominates(edge.node()->owner, m_block);
if (verbose) {
dataLog(
- "Checking if ", edge, " in ", *edge.node()->misc.owner,
+ "Checking if ", edge, " in ", *edge.node()->owner,
" dominates ", *m_block, ": ", result, "\n");
}
m_result &= result;
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index 79d6593..57a6559 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -1296,22 +1296,24 @@
return true;
}
- bool isStringPrototypeMethodSane(Structure* stringPrototypeStructure, StringImpl* uid)
+ bool isStringPrototypeMethodSane(
+ JSObject* stringPrototype, Structure* stringPrototypeStructure, StringImpl* uid)
{
unsigned attributesUnused;
- JSCell* specificValue;
- PropertyOffset offset = stringPrototypeStructure->getConcurrently(
- vm(), uid, attributesUnused, specificValue);
+ PropertyOffset offset =
+ stringPrototypeStructure->getConcurrently(vm(), uid, attributesUnused);
if (!isValidOffset(offset))
return false;
- if (!specificValue)
+ JSValue value = m_graph.tryGetConstantProperty(
+ stringPrototype, stringPrototypeStructure, offset);
+ if (!value)
return false;
- if (!specificValue->inherits(JSFunction::info()))
+ JSFunction* function = jsDynamicCast<JSFunction*>(value);
+ if (!function)
return false;
- JSFunction* function = jsCast<JSFunction*>(specificValue);
if (function->executable()->intrinsicFor(CodeForCall) != StringPrototypeValueOfIntrinsic)
return false;
@@ -1340,9 +1342,9 @@
// (that would call toString()). We don't want the DFG to have to distinguish
// between the two, just because that seems like it would get confusing. So we
// just require both methods to be sane.
- if (!isStringPrototypeMethodSane(stringPrototypeStructure, vm().propertyNames->valueOf.impl()))
+ if (!isStringPrototypeMethodSane(stringPrototypeObject, stringPrototypeStructure, vm().propertyNames->valueOf.impl()))
return false;
- if (!isStringPrototypeMethodSane(stringPrototypeStructure, vm().propertyNames->toString.impl()))
+ if (!isStringPrototypeMethodSane(stringPrototypeObject, stringPrototypeStructure, vm().propertyNames->toString.impl()))
return false;
return true;
diff --git a/Source/JavaScriptCore/dfg/DFGGraph.cpp b/Source/JavaScriptCore/dfg/DFGGraph.cpp
index b4e12f5..6d0a864 100644
--- a/Source/JavaScriptCore/dfg/DFGGraph.cpp
+++ b/Source/JavaScriptCore/dfg/DFGGraph.cpp
@@ -639,25 +639,75 @@
}
}
-void Graph::addForDepthFirstSort(Vector<BasicBlock*>& result, Vector<BasicBlock*, 16>& worklist, HashSet<BasicBlock*>& seen, BasicBlock* block)
+// Utilities for pre- and post-order traversals.
+namespace {
+
+inline void addForPreOrder(Vector<BasicBlock*>& result, Vector<BasicBlock*, 16>& worklist, BitVector& seen, BasicBlock* block)
{
- if (seen.contains(block))
+ if (seen.get(block->index))
return;
result.append(block);
worklist.append(block);
- seen.add(block);
+ seen.set(block->index);
}
-void Graph::getBlocksInDepthFirstOrder(Vector<BasicBlock*>& result)
+enum PostOrderTaskKind {
+ PostOrderFirstVisit,
+ PostOrderAddToResult
+};
+
+struct PostOrderTask {
+ PostOrderTask(BasicBlock* block = nullptr, PostOrderTaskKind kind = PostOrderFirstVisit)
+ : m_block(block)
+ , m_kind(kind)
+ {
+ }
+
+ BasicBlock* m_block;
+ PostOrderTaskKind m_kind;
+};
+
+inline void addForPostOrder(Vector<PostOrderTask, 16>& worklist, BitVector& seen, BasicBlock* block)
+{
+ if (seen.get(block->index))
+ return;
+
+ worklist.append(PostOrderTask(block, PostOrderFirstVisit));
+ seen.set(block->index);
+}
+
+} // anonymous namespace
+
+void Graph::getBlocksInPreOrder(Vector<BasicBlock*>& result)
{
Vector<BasicBlock*, 16> worklist;
- HashSet<BasicBlock*> seen;
- addForDepthFirstSort(result, worklist, seen, block(0));
+ BitVector seen;
+ addForPreOrder(result, worklist, seen, block(0));
while (!worklist.isEmpty()) {
BasicBlock* block = worklist.takeLast();
for (unsigned i = block->numSuccessors(); i--;)
- addForDepthFirstSort(result, worklist, seen, block->successor(i));
+ addForPreOrder(result, worklist, seen, block->successor(i));
+ }
+}
+
+void Graph::getBlocksInPostOrder(Vector<BasicBlock*>& result)
+{
+ Vector<PostOrderTask, 16> worklist;
+ BitVector seen;
+ addForPostOrder(worklist, seen, block(0));
+ while (!worklist.isEmpty()) {
+ PostOrderTask task = worklist.takeLast();
+ switch (task.m_kind) {
+ case PostOrderFirstVisit:
+ worklist.append(PostOrderTask(task.m_block, PostOrderAddToResult));
+ for (unsigned i = task.m_block->numSuccessors(); i--;)
+ addForPostOrder(worklist, seen, task.m_block->successor(i));
+ break;
+ case PostOrderAddToResult:
+ result.append(task.m_block);
+ break;
+ }
}
}
@@ -668,9 +718,9 @@
if (!block)
continue;
for (unsigned phiIndex = block->phis.size(); phiIndex--;)
- block->phis[phiIndex]->misc.replacement = 0;
+ block->phis[phiIndex]->replacement = 0;
for (unsigned nodeIndex = block->size(); nodeIndex--;)
- block->at(nodeIndex)->misc.replacement = 0;
+ block->at(nodeIndex)->replacement = 0;
}
}
@@ -681,9 +731,9 @@
if (!block)
continue;
for (unsigned phiIndex = block->phis.size(); phiIndex--;)
- block->phis[phiIndex]->misc.owner = block;
+ block->phis[phiIndex]->owner = block;
for (unsigned nodeIndex = block->size(); nodeIndex--;)
- block->at(nodeIndex)->misc.owner = block;
+ block->at(nodeIndex)->owner = block;
}
}
@@ -789,6 +839,71 @@
return std::max(frameRegisterCount(), requiredRegisterCountForExit());
}
+JSValue Graph::tryGetConstantProperty(
+ JSValue base, const StructureSet& structureSet, PropertyOffset offset)
+{
+ if (!base || !base.isObject())
+ return JSValue();
+
+ JSObject* object = asObject(base);
+
+ for (unsigned i = structureSet.size(); i--;) {
+ Structure* structure = structureSet[i];
+ WatchpointSet* set = structure->propertyReplacementWatchpointSet(offset);
+ if (!set || !set->isStillValid())
+ return JSValue();
+
+ ASSERT(structure->isValidOffset(offset));
+ ASSERT(!structure->isUncacheableDictionary());
+
+ watchpoints().addLazily(set);
+ }
+
+ // What follows may require some extra thought. We need this load to load a valid JSValue. If
+ // our profiling makes sense and we're still on track to generate code that won't be
+ // invalidated, then we have nothing to worry about. We do, however, have to worry about
+ // loading - and then using - an invalid JSValue in the case that unbeknownst to us our code
+ // is doomed.
+ //
+ // One argument in favor of this code is that it should definitely work because the butterfly
+ // is always set before the structure. However, we don't currently have a fence between those
+ // stores. It's not clear if this matters, however. We don't ever shrink the property storage.
+ // So, for this to fail, you'd need an access on a constant object pointer such that the inline
+ // caches told us that the object had a structure that it did not *yet* have, and then later,
+ // the object transitioned to that structure that the inline caches had alraedy seen. And then
+ // the processor reordered the stores. Seems unlikely and difficult to test. I believe that
+ // this is worth revisiting but it isn't worth losing sleep over. Filed:
+ // https://bugs.webkit.org/show_bug.cgi?id=134641
+ //
+ // For now, we just do the minimal thing: defend against the structure right now being
+ // incompatible with the getDirect we're trying to do. The easiest way to do that is to
+ // determine if the structure belongs to the proven set.
+
+ if (!structureSet.contains(object->structure()))
+ return JSValue();
+
+ return object->getDirect(offset);
+}
+
+JSValue Graph::tryGetConstantProperty(JSValue base, Structure* structure, PropertyOffset offset)
+{
+ return tryGetConstantProperty(base, StructureSet(structure), offset);
+}
+
+JSValue Graph::tryGetConstantProperty(
+ JSValue base, const StructureAbstractValue& structure, PropertyOffset offset)
+{
+ if (structure.isTop() || structure.isClobbered())
+ return JSValue();
+
+ return tryGetConstantProperty(base, structure.set(), offset);
+}
+
+JSValue Graph::tryGetConstantProperty(const AbstractValue& base, PropertyOffset offset)
+{
+ return tryGetConstantProperty(base.m_value, base.m_structure, offset);
+}
+
JSActivation* Graph::tryGetActivation(Node* node)
{
return node->dynamicCastConstant<JSActivation*>();
@@ -900,7 +1015,6 @@
case MultiGetByOffset:
for (unsigned i = node->multiGetByOffsetData().variants.size(); i--;) {
GetByIdVariant& variant = node->multiGetByOffsetData().variants[i];
- visitor.appendUnbarrieredReadOnlyValue(variant.specificValue());
const StructureSet& set = variant.structureSet();
for (unsigned j = set.size(); j--;)
visitor.appendUnbarrieredReadOnlyPointer(set[j]);
diff --git a/Source/JavaScriptCore/dfg/DFGGraph.h b/Source/JavaScriptCore/dfg/DFGGraph.h
index 6d50960..e612642 100644
--- a/Source/JavaScriptCore/dfg/DFGGraph.h
+++ b/Source/JavaScriptCore/dfg/DFGGraph.h
@@ -125,7 +125,7 @@
return;
// Check if there is any replacement.
- Node* replacement = child->misc.replacement;
+ Node* replacement = child->replacement;
if (!replacement)
return;
@@ -133,7 +133,7 @@
// There is definitely a replacement. Assert that the replacement does not
// have a replacement.
- ASSERT(!child->misc.replacement);
+ ASSERT(!child->replacement);
}
template<typename... Params>
@@ -675,7 +675,8 @@
void clearReplacements();
void initializeNodeOwners();
- void getBlocksInDepthFirstOrder(Vector<BasicBlock*>& result);
+ void getBlocksInPreOrder(Vector<BasicBlock*>& result);
+ void getBlocksInPostOrder(Vector<BasicBlock*>& result);
Profiler::Compilation* compilation() { return m_plan.compilation.get(); }
@@ -691,6 +692,11 @@
unsigned requiredRegisterCountForExit();
unsigned requiredRegisterCountForExecutionAndExit();
+ JSValue tryGetConstantProperty(JSValue base, const StructureSet&, PropertyOffset);
+ JSValue tryGetConstantProperty(JSValue base, Structure*, PropertyOffset);
+ JSValue tryGetConstantProperty(JSValue base, const StructureAbstractValue&, PropertyOffset);
+ JSValue tryGetConstantProperty(const AbstractValue&, PropertyOffset);
+
JSActivation* tryGetActivation(Node*);
WriteBarrierBase<Unknown>* tryGetRegisters(Node*);
@@ -758,7 +764,6 @@
private:
void handleSuccessor(Vector<BasicBlock*, 16>& worklist, BasicBlock*, BasicBlock* successor);
- void addForDepthFirstSort(Vector<BasicBlock*>& result, Vector<BasicBlock*, 16>& worklist, HashSet<BasicBlock*>& seen, BasicBlock*);
AddSpeculationMode addImmediateShouldSpeculateInt32(Node* add, bool variableShouldSpeculateInt32, Node* immediate, RareCaseProfilingSource source)
{
diff --git a/Source/JavaScriptCore/dfg/DFGHeapLocation.cpp b/Source/JavaScriptCore/dfg/DFGHeapLocation.cpp
new file mode 100644
index 0000000..b89d463
--- /dev/null
+++ b/Source/JavaScriptCore/dfg/DFGHeapLocation.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 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 "DFGHeapLocation.h"
+
+#if ENABLE(DFG_JIT)
+
+namespace JSC { namespace DFG {
+
+void HeapLocation::dump(PrintStream& out) const
+{
+ out.print(m_kind, ":", m_heap);
+
+ if (!m_base)
+ return;
+
+ out.print("[", m_base);
+ if (m_index)
+ out.print(", ", m_index);
+ out.print("]");
+}
+
+} } // namespace JSC::DFG
+
+namespace WTF {
+
+using namespace JSC::DFG;
+
+void printInternal(PrintStream& out, LocationKind kind)
+{
+ switch (kind) {
+ case InvalidLocationKind:
+ out.print("InvalidLocationKind");
+ return;
+
+ case InvalidationPointLoc:
+ out.print("InvalidationPointLoc");
+ return;
+
+ case IsObjectLoc:
+ out.print("IsObjectLoc");
+ return;
+
+ case IsFunctionLoc:
+ out.print("IsFunctionLoc");
+ return;
+
+ case TypeOfLoc:
+ out.print("TypeOfLoc");
+ return;
+
+ case GetterLoc:
+ out.print("GetterLoc");
+ return;
+
+ case SetterLoc:
+ out.print("SetterLoc");
+ return;
+
+ case VariableLoc:
+ out.print("VariableLoc");
+ return;
+
+ case ArrayLengthLoc:
+ out.print("ArrayLengthLoc");
+ return;
+
+ case ButterflyLoc:
+ out.print("ButterflyLoc");
+ return;
+
+ case CheckHasInstanceLoc:
+ out.print("CheckHasInstanceLoc");
+ return;
+
+ case ClosureRegistersLoc:
+ out.print("ClosureRegistersLoc");
+ return;
+
+ case ClosureVariableLoc:
+ out.print("ClosureVariableLoc");
+ return;
+
+ case GlobalVariableLoc:
+ out.print("GlobalVariableLoc");
+ return;
+
+ case IndexedPropertyLoc:
+ out.print("IndexedPorpertyLoc");
+ return;
+
+ case IndexedPropertyStorageLoc:
+ out.print("IndexedPropertyStorageLoc");
+ return;
+
+ case InstanceOfLoc:
+ out.print("InstanceOfLoc");
+ return;
+
+ case MyArgumentByValLoc:
+ out.print("MyArgumentByValLoc");
+ return;
+
+ case MyArgumentsLengthLoc:
+ out.print("MyArgumentsLengthLoc");
+ return;
+
+ case NamedPropertyLoc:
+ out.print("NamedPropertyLoc");
+ return;
+
+ case SkipTopScopeLoc:
+ out.print("SkipTopScopeLoc");
+ return;
+
+ case TypedArrayByteOffsetLoc:
+ out.print("TypedArrayByteOffsetLoc");
+ return;
+
+ case VarInjectionWatchpointLoc:
+ out.print("VarInjectionWatchpointLoc");
+ return;
+
+ case AllocationProfileWatchpointLoc:
+ out.print("AllocationProfileWatchpointLoc");
+ return;
+ }
+
+ RELEASE_ASSERT_NOT_REACHED();
+}
+
+} // namespace WTF
+
+#endif // ENABLE(DFG_JIT)
+
diff --git a/Source/JavaScriptCore/dfg/DFGHeapLocation.h b/Source/JavaScriptCore/dfg/DFGHeapLocation.h
new file mode 100644
index 0000000..3b08d76
--- /dev/null
+++ b/Source/JavaScriptCore/dfg/DFGHeapLocation.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#ifndef DFGHeapLocation_h
+#define DFGHeapLocation_h
+
+#if ENABLE(DFG_JIT)
+
+#include "DFGAbstractHeap.h"
+#include "DFGNode.h"
+
+namespace JSC { namespace DFG {
+
+enum LocationKind {
+ InvalidLocationKind,
+
+ AllocationProfileWatchpointLoc,
+ ArrayLengthLoc,
+ ButterflyLoc,
+ CheckHasInstanceLoc,
+ ClosureRegistersLoc,
+ ClosureVariableLoc,
+ GetterLoc,
+ GlobalVariableLoc,
+ IndexedPropertyLoc,
+ IndexedPropertyStorageLoc,
+ InstanceOfLoc,
+ InvalidationPointLoc,
+ IsFunctionLoc,
+ IsObjectLoc,
+ MyArgumentByValLoc,
+ MyArgumentsLengthLoc,
+ NamedPropertyLoc,
+ SetterLoc,
+ SkipTopScopeLoc,
+ TypeOfLoc,
+ TypedArrayByteOffsetLoc,
+ VarInjectionWatchpointLoc,
+ VariableLoc
+};
+
+class HeapLocation {
+public:
+ HeapLocation(
+ LocationKind kind = InvalidLocationKind,
+ AbstractHeap heap = AbstractHeap(),
+ Node* base = nullptr, Node* index = nullptr)
+ : m_kind(kind)
+ , m_heap(heap)
+ , m_base(base)
+ , m_index(index)
+ {
+ ASSERT((kind == InvalidLocationKind) == !heap);
+ ASSERT(!!m_heap || !m_base);
+ ASSERT(m_base || !m_index);
+ }
+
+ HeapLocation(LocationKind kind, AbstractHeap heap, Edge base, Edge index = Edge())
+ : HeapLocation(kind, heap, base.node(), index.node())
+ {
+ }
+
+ HeapLocation(WTF::HashTableDeletedValueType)
+ : m_kind(InvalidLocationKind)
+ , m_heap(WTF::HashTableDeletedValue)
+ , m_base(nullptr)
+ , m_index(nullptr)
+ {
+ }
+
+ bool operator!() const { return !m_heap; }
+
+ LocationKind kind() const { return m_kind; }
+ AbstractHeap heap() const { return m_heap; }
+ Node* base() const { return m_base; }
+ Node* index() const { return m_index; }
+
+ unsigned hash() const
+ {
+ return m_kind + m_heap.hash() + WTF::PtrHash<Node*>::hash(m_index) + m_kind;
+ }
+
+ bool operator==(const HeapLocation& other) const
+ {
+ return m_kind == other.m_kind
+ && m_heap == other.m_heap
+ && m_base == other.m_base
+ && m_index == other.m_index;
+ }
+
+ bool isHashTableDeletedValue() const
+ {
+ return m_heap.isHashTableDeletedValue();
+ }
+
+ void dump(PrintStream& out) const;
+
+private:
+ LocationKind m_kind;
+ AbstractHeap m_heap;
+ Node* m_base;
+ Node* m_index;
+};
+
+struct HeapLocationHash {
+ static unsigned hash(const HeapLocation& key) { return key.hash(); }
+ static bool equal(const HeapLocation& a, const HeapLocation& b) { return a == b; }
+ static const bool safeToCompareToEmptyOrDeleted = true;
+};
+
+} } // namespace JSC::DFG
+
+namespace WTF {
+
+void printInternal(PrintStream&, JSC::DFG::LocationKind);
+
+template<typename T> struct DefaultHash;
+template<> struct DefaultHash<JSC::DFG::HeapLocation> {
+ typedef JSC::DFG::HeapLocationHash Hash;
+};
+
+template<typename T> struct HashTraits;
+template<> struct HashTraits<JSC::DFG::HeapLocation> : SimpleClassHashTraits<JSC::DFG::HeapLocation> {
+ static const bool emptyValueIsZero = false;
+};
+
+} // namespace WTF
+
+namespace JSC { namespace DFG {
+
+typedef HashMap<HeapLocation, Node*> ImpureMap;
+
+} } // namespace JSC::DFG
+
+#endif // ENABLE(DFG_JIT)
+
+#endif // DFGHeapLocation_h
+
diff --git a/Source/JavaScriptCore/dfg/DFGLICMPhase.cpp b/Source/JavaScriptCore/dfg/DFGLICMPhase.cpp
index 5d12da6..cc2c883 100644
--- a/Source/JavaScriptCore/dfg/DFGLICMPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGLICMPhase.cpp
@@ -151,7 +151,7 @@
// For maximum profit, we walk blocks in DFS order to ensure that we generally
// tend to hoist dominators before dominatees.
Vector<BasicBlock*> depthFirst;
- m_graph.getBlocksInDepthFirstOrder(depthFirst);
+ m_graph.getBlocksInPreOrder(depthFirst);
Vector<const NaturalLoop*> loopStack;
bool changed = false;
for (
@@ -245,7 +245,7 @@
}
data.preHeader->insertBeforeLast(node);
- node->misc.owner = data.preHeader;
+ node->owner = data.preHeader;
NodeOrigin originalOrigin = node->origin;
node->origin.forExit = data.preHeader->last()->origin.forExit;
diff --git a/Source/JavaScriptCore/dfg/DFGNode.h b/Source/JavaScriptCore/dfg/DFGNode.h
index 4f4b976..5e0b0af 100644
--- a/Source/JavaScriptCore/dfg/DFGNode.h
+++ b/Source/JavaScriptCore/dfg/DFGNode.h
@@ -208,8 +208,9 @@
, m_virtualRegister(VirtualRegister())
, m_refCount(1)
, m_prediction(SpecNone)
+ , replacement(nullptr)
+ , owner(nullptr)
{
- misc.replacement = 0;
setOpAndDefaultFlags(op);
}
@@ -222,8 +223,9 @@
, m_prediction(SpecNone)
, m_opInfo(0)
, m_opInfo2(0)
+ , replacement(nullptr)
+ , owner(nullptr)
{
- misc.replacement = 0;
setOpAndDefaultFlags(op);
ASSERT(!(m_flags & NodeHasVarArgs));
}
@@ -237,8 +239,9 @@
, m_prediction(SpecNone)
, m_opInfo(0)
, m_opInfo2(0)
+ , replacement(nullptr)
+ , owner(nullptr)
{
- misc.replacement = 0;
setOpAndDefaultFlags(op);
setResult(result);
ASSERT(!(m_flags & NodeHasVarArgs));
@@ -253,8 +256,9 @@
, m_prediction(SpecNone)
, m_opInfo(imm.m_value)
, m_opInfo2(0)
+ , replacement(nullptr)
+ , owner(nullptr)
{
- misc.replacement = 0;
setOpAndDefaultFlags(op);
ASSERT(!(m_flags & NodeHasVarArgs));
}
@@ -268,8 +272,9 @@
, m_prediction(SpecNone)
, m_opInfo(imm.m_value)
, m_opInfo2(0)
+ , replacement(nullptr)
+ , owner(nullptr)
{
- misc.replacement = 0;
setOpAndDefaultFlags(op);
setResult(result);
ASSERT(!(m_flags & NodeHasVarArgs));
@@ -284,8 +289,9 @@
, m_prediction(SpecNone)
, m_opInfo(imm1.m_value)
, m_opInfo2(imm2.m_value)
+ , replacement(nullptr)
+ , owner(nullptr)
{
- misc.replacement = 0;
setOpAndDefaultFlags(op);
ASSERT(!(m_flags & NodeHasVarArgs));
}
@@ -299,8 +305,9 @@
, m_prediction(SpecNone)
, m_opInfo(imm1.m_value)
, m_opInfo2(imm2.m_value)
+ , replacement(nullptr)
+ , owner(nullptr)
{
- misc.replacement = 0;
setOpAndDefaultFlags(op);
ASSERT(m_flags & NodeHasVarArgs);
}
@@ -366,10 +373,16 @@
{
setOpAndDefaultFlags(Phantom);
}
-
- void convertToPhantomUnchecked()
+
+ void convertToCheck()
{
- setOpAndDefaultFlags(Phantom);
+ setOpAndDefaultFlags(Check);
+ }
+
+ void replaceWith(Node* other)
+ {
+ convertToPhantom();
+ replacement = other;
}
void convertToIdentity();
@@ -436,6 +449,7 @@
ASSERT(op() == GetIndexedPropertyStorage);
m_op = ConstantStoragePointer;
m_opInfo = bitwise_cast<uintptr_t>(pointer);
+ children.reset();
}
void convertToGetLocalUnlinked(VirtualRegister local)
@@ -1760,19 +1774,17 @@
AbstractValue value;
// Miscellaneous data that is usually meaningless, but can hold some analysis results
- // if you ask right. For example, if you do Graph::initializeNodeOwners(), misc.owner
+ // if you ask right. For example, if you do Graph::initializeNodeOwners(), Node::owner
// will tell you which basic block a node belongs to. You cannot rely on this persisting
// across transformations unless you do the maintenance work yourself. Other phases use
- // misc.replacement, but they do so manually: first you do Graph::clearReplacements()
+ // Node::replacement, but they do so manually: first you do Graph::clearReplacements()
// and then you set, and use, replacement's yourself.
//
// Bottom line: don't use these fields unless you initialize them yourself, or by
// calling some appropriate methods that initialize them the way you want. Otherwise,
// these fields are meaningless.
- union {
- Node* replacement;
- BasicBlock* owner;
- } misc;
+ Node* replacement;
+ BasicBlock* owner;
};
inline bool nodeComparator(Node* a, Node* b)
diff --git a/Source/JavaScriptCore/dfg/DFGOSREntry.cpp b/Source/JavaScriptCore/dfg/DFGOSREntry.cpp
index e5cd42d..cbaa0a8 100644
--- a/Source/JavaScriptCore/dfg/DFGOSREntry.cpp
+++ b/Source/JavaScriptCore/dfg/DFGOSREntry.cpp
@@ -58,6 +58,9 @@
sanitizeStackForVM(vm);
+ if (bytecodeIndex)
+ codeBlock->ownerExecutable()->setDidTryToEnterInLoop(true);
+
if (codeBlock->jitType() != JITCode::DFGJIT) {
RELEASE_ASSERT(codeBlock->jitType() == JITCode::FTLJIT);
diff --git a/Source/JavaScriptCore/dfg/DFGOSRExitCompilerCommon.cpp b/Source/JavaScriptCore/dfg/DFGOSRExitCompilerCommon.cpp
index b9ec462..ae3d165 100644
--- a/Source/JavaScriptCore/dfg/DFGOSRExitCompilerCommon.cpp
+++ b/Source/JavaScriptCore/dfg/DFGOSRExitCompilerCommon.cpp
@@ -54,20 +54,55 @@
AssemblyHelpers::GreaterThanOrEqual,
AssemblyHelpers::Address(GPRInfo::regT0, CodeBlock::offsetOfJITExecuteCounter()),
AssemblyHelpers::TrustedImm32(0));
+
+ // We want to figure out if there's a possibility that we're in a loop. For the outermost
+ // code block in the inline stack, we handle this appropriately by having the loop OSR trigger
+ // check the exit count of the replacement of the CodeBlock from which we are OSRing. The
+ // problem is the inlined functions, which might also have loops, but whose baseline versions
+ // don't know where to look for the exit count. Figure out if those loops are severe enough
+ // that we had tried to OSR enter. If so, then we should use the loop reoptimization trigger.
+ // Otherwise, we should use the normal reoptimization trigger.
+
+ AssemblyHelpers::JumpList loopThreshold;
+
+ for (InlineCallFrame* inlineCallFrame = exit.m_codeOrigin.inlineCallFrame; inlineCallFrame; inlineCallFrame = inlineCallFrame->caller.inlineCallFrame) {
+ loopThreshold.append(
+ jit.branchTest8(
+ AssemblyHelpers::NonZero,
+ AssemblyHelpers::AbsoluteAddress(
+ inlineCallFrame->executable->addressOfDidTryToEnterInLoop())));
+ }
+
+ jit.move(
+ AssemblyHelpers::TrustedImm32(jit.codeBlock()->exitCountThresholdForReoptimization()),
+ GPRInfo::regT1);
+
+ if (!loopThreshold.empty()) {
+ AssemblyHelpers::Jump done = jit.jump();
+
+ loopThreshold.link(&jit);
+ jit.move(
+ AssemblyHelpers::TrustedImm32(
+ jit.codeBlock()->exitCountThresholdForReoptimizationFromLoop()),
+ GPRInfo::regT1);
- tooFewFails = jit.branch32(AssemblyHelpers::BelowOrEqual, GPRInfo::regT2, AssemblyHelpers::TrustedImm32(jit.codeBlock()->exitCountThresholdForReoptimization()));
+ done.link(&jit);
+ }
+
+ tooFewFails = jit.branch32(AssemblyHelpers::BelowOrEqual, GPRInfo::regT2, GPRInfo::regT1);
reoptimizeNow.link(&jit);
// Reoptimize as soon as possible.
#if !NUMBER_OF_ARGUMENT_REGISTERS
jit.poke(GPRInfo::regT0);
+ jit.poke(AssemblyHelpers::TrustedImmPtr(&exit), 1);
#else
jit.move(GPRInfo::regT0, GPRInfo::argumentGPR0);
- ASSERT(GPRInfo::argumentGPR0 != GPRInfo::regT1);
+ jit.move(AssemblyHelpers::TrustedImmPtr(&exit), GPRInfo::argumentGPR1);
#endif
- jit.move(AssemblyHelpers::TrustedImmPtr(bitwise_cast<void*>(triggerReoptimizationNow)), GPRInfo::regT1);
- jit.call(GPRInfo::regT1);
+ jit.move(AssemblyHelpers::TrustedImmPtr(bitwise_cast<void*>(triggerReoptimizationNow)), GPRInfo::nonArgGPR0);
+ jit.call(GPRInfo::nonArgGPR0);
AssemblyHelpers::Jump doneAdjusting = jit.jump();
tooFewFails.link(&jit);
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp
index 15cb4f5..824ae46 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.cpp
+++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp
@@ -1030,7 +1030,7 @@
NativeCallFrameTracer tracer(&vm, exec);
JSValue value = JSValue::decode(encodedValue);
- set->notifyWrite(vm, value);
+ set->notifyWrite(vm, value, "Executed NotifyWrite");
}
double JIT_OPERATION operationFModOnInts(int32_t a, int32_t b)
@@ -1105,7 +1105,7 @@
dataLog("\n");
}
-extern "C" void JIT_OPERATION triggerReoptimizationNow(CodeBlock* codeBlock)
+extern "C" void JIT_OPERATION triggerReoptimizationNow(CodeBlock* codeBlock, OSRExitBase* exit)
{
// It's sort of preferable that we don't GC while in here. Anyways, doing so wouldn't
// really be profitable.
@@ -1129,13 +1129,21 @@
ASSERT(codeBlock->hasOptimizedReplacement());
CodeBlock* optimizedCodeBlock = codeBlock->replacement();
ASSERT(JITCode::isOptimizingJIT(optimizedCodeBlock->jitType()));
+
+ bool didTryToEnterIntoInlinedLoops = false;
+ for (InlineCallFrame* inlineCallFrame = exit->m_codeOrigin.inlineCallFrame; inlineCallFrame; inlineCallFrame = inlineCallFrame->caller.inlineCallFrame) {
+ if (inlineCallFrame->executable->didTryToEnterInLoop()) {
+ didTryToEnterIntoInlinedLoops = true;
+ break;
+ }
+ }
// In order to trigger reoptimization, one of two things must have happened:
// 1) We exited more than some number of times.
// 2) We exited and got stuck in a loop, and now we're exiting again.
bool didExitABunch = optimizedCodeBlock->shouldReoptimizeNow();
bool didGetStuckInLoop =
- codeBlock->checkIfOptimizationThresholdReached()
+ (codeBlock->checkIfOptimizationThresholdReached() || didTryToEnterIntoInlinedLoops)
&& optimizedCodeBlock->shouldReoptimizeFromLoopNow();
if (!didExitABunch && !didGetStuckInLoop) {
@@ -1228,7 +1236,7 @@
if (Options::verboseOSR()) {
dataLog(
- *codeBlock, ": Entered triggerTierUpNow with executeCounter = ",
+ *codeBlock, ": Entered triggerOSREntryNow with executeCounter = ",
jitCode->tierUpCounter, "\n");
}
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.h b/Source/JavaScriptCore/dfg/DFGOperations.h
index 720b60a..f78c9b0 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.h
+++ b/Source/JavaScriptCore/dfg/DFGOperations.h
@@ -31,9 +31,9 @@
#include "JITOperations.h"
#include "PutKind.h"
-namespace JSC {
+namespace JSC { namespace DFG {
-namespace DFG {
+struct OSRExitBase;
extern "C" {
@@ -136,7 +136,7 @@
void JIT_OPERATION debugOperationPrintSpeculationFailure(ExecState*, void*, void*) WTF_INTERNAL;
-void JIT_OPERATION triggerReoptimizationNow(CodeBlock*) WTF_INTERNAL;
+void JIT_OPERATION triggerReoptimizationNow(CodeBlock*, OSRExitBase*) WTF_INTERNAL;
#if ENABLE(FTL_JIT)
void JIT_OPERATION triggerTierUpNow(ExecState*) WTF_INTERNAL;
diff --git a/Source/JavaScriptCore/dfg/DFGPhantomRemovalPhase.cpp b/Source/JavaScriptCore/dfg/DFGPhantomRemovalPhase.cpp
new file mode 100644
index 0000000..e3cac84
--- /dev/null
+++ b/Source/JavaScriptCore/dfg/DFGPhantomRemovalPhase.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2014 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 "DFGPhantomRemovalPhase.h"
+
+#if ENABLE(DFG_JIT)
+
+#include "DFGGraph.h"
+#include "DFGInsertionSet.h"
+#include "DFGPhase.h"
+#include "DFGPredictionPropagationPhase.h"
+#include "DFGVariableAccessDataDump.h"
+#include "JSCInlines.h"
+
+namespace JSC { namespace DFG {
+
+class PhantomRemovalPhase : public Phase {
+public:
+ PhantomRemovalPhase(Graph& graph)
+ : Phase(graph, "phantom removal")
+ {
+ }
+
+ bool run()
+ {
+ bool changed = false;
+
+ for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
+ BasicBlock* block = m_graph.block(blockIndex);
+ if (!block)
+ continue;
+
+ // All Phis need to already be marked as relevant to OSR.
+ if (!ASSERT_DISABLED) {
+ for (unsigned i = 0; i < block->phis.size(); ++i)
+ ASSERT(block->phis[i]->flags() & NodeRelevantToOSR);
+ }
+
+ for (unsigned i = block->size(); i--;) {
+ Node* node = block->at(i);
+
+ switch (node->op()) {
+ case SetLocal:
+ case GetLocal: // FIXME: The GetLocal case is only necessary until we do https://bugs.webkit.org/show_bug.cgi?id=106707.
+ node->mergeFlags(NodeRelevantToOSR);
+ break;
+ default:
+ node->clearFlags(NodeRelevantToOSR);
+ break;
+ }
+ }
+ }
+
+ for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
+ BasicBlock* block = m_graph.block(blockIndex);
+ if (!block)
+ continue;
+
+ for (unsigned i = block->size(); i--;) {
+ Node* node = block->at(i);
+ if (node->op() == MovHint)
+ node->child1()->mergeFlags(NodeRelevantToOSR);
+ }
+ }
+
+ for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
+ BasicBlock* block = m_graph.block(blockIndex);
+ if (!block)
+ continue;
+
+ unsigned sourceIndex = 0;
+ unsigned targetIndex = 0;
+ Node* lastNode = nullptr;
+ while (sourceIndex < block->size()) {
+ Node* node = block->at(sourceIndex++);
+ if (node->op() == Phantom) {
+ if (lastNode && (lastNode->origin.forExit != node->origin.forExit || (lastNode->flags() & NodeHasVarArgs)))
+ lastNode = nullptr;
+ for (unsigned i = 0; i < AdjacencyList::Size; ++i) {
+ Edge edge = node->children.child(i);
+ if (!edge)
+ break;
+ if (edge.useKind() != UntypedUse)
+ continue; // Keep the type check.
+ if (edge->flags() & NodeRelevantToOSR) {
+ bool found = false;
+ if (lastNode) {
+ for (unsigned j = 0; j < AdjacencyList::Size; ++j) {
+ if (lastNode->children.child(j).node() == edge.node()) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found)
+ continue;
+ }
+
+ node->children.removeEdge(i--);
+ changed = true;
+ }
+
+ if (node->children.isEmpty())
+ continue;
+ }
+ lastNode = node;
+ block->at(targetIndex++) = node;
+ }
+ block->resize(targetIndex);
+ }
+
+ return changed;
+ }
+};
+
+bool performPhantomRemoval(Graph& graph)
+{
+ SamplingRegion samplingRegion("DFG Phantom Removal Phase");
+ return runPhase<PhantomRemovalPhase>(graph);
+}
+
+} } // namespace JSC::DFG
+
+#endif // ENABLE(DFG_JIT)
+
diff --git a/Source/JavaScriptCore/dfg/DFGPhantomRemovalPhase.h b/Source/JavaScriptCore/dfg/DFGPhantomRemovalPhase.h
new file mode 100644
index 0000000..f0dd98c
--- /dev/null
+++ b/Source/JavaScriptCore/dfg/DFGPhantomRemovalPhase.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#ifndef DFGPhantomRemovalPhase_h
+#define DFGPhantomRemovalPhase_h
+
+#if ENABLE(DFG_JIT)
+
+namespace JSC { namespace DFG {
+
+class Graph;
+
+// Cleans up unnecessary Phantoms and Phanton children. This reduces live ranges, but also, it
+// eliminates many Phantoms entirely. This invalidates liveness analysis.
+
+bool performPhantomRemoval(Graph&);
+
+} } // namespace JSC::DFG
+
+#endif // ENABLE(DFG_JIT)
+
+#endif // DFGPhantomRemovalPhase_h
diff --git a/Source/JavaScriptCore/dfg/DFGPlan.cpp b/Source/JavaScriptCore/dfg/DFGPlan.cpp
index a9ad6e5..d72945c 100644
--- a/Source/JavaScriptCore/dfg/DFGPlan.cpp
+++ b/Source/JavaScriptCore/dfg/DFGPlan.cpp
@@ -49,6 +49,7 @@
#include "DFGLoopPreHeaderCreationPhase.h"
#include "DFGOSRAvailabilityAnalysisPhase.h"
#include "DFGOSREntrypointCreationPhase.h"
+#include "DFGPhantomRemovalPhase.h"
#include "DFGPredictionInjectionPhase.h"
#include "DFGPredictionPropagationPhase.h"
#include "DFGResurrectionForValidationPhase.h"
@@ -250,18 +251,18 @@
validate(dfg);
performStrengthReduction(dfg);
- performCSE(dfg);
+ performLocalCSE(dfg);
performArgumentsSimplification(dfg);
performCPSRethreading(dfg);
performCFA(dfg);
performConstantFolding(dfg);
bool changed = false;
changed |= performCFGSimplification(dfg);
- changed |= performCSE(dfg);
+ changed |= performLocalCSE(dfg);
if (validationEnabled())
validate(dfg);
-
+
performCPSRethreading(dfg);
if (changed) {
performCFA(dfg);
@@ -282,6 +283,7 @@
performTierUpCheckInjection(dfg);
performStoreBarrierElision(dfg);
+ performPhantomRemoval(dfg);
performCPSRethreading(dfg);
performDCE(dfg);
performStackLayout(dfg);
@@ -309,17 +311,13 @@
return FailPath;
}
+ performPhantomRemoval(dfg);
performCriticalEdgeBreaking(dfg);
performLoopPreHeaderCreation(dfg);
performCPSRethreading(dfg);
performSSAConversion(dfg);
performSSALowering(dfg);
- performCSE(dfg);
-
- // At this point we're not allowed to do any further code motion because our reasoning
- // about code motion assumes that it's OK to insert GC points in random places.
-
- performStoreBarrierElision(dfg);
+ performGlobalCSE(dfg);
performLivenessAnalysis(dfg);
performCFA(dfg);
performConstantFolding(dfg);
@@ -330,14 +328,16 @@
performCFA(dfg);
}
performLICM(dfg);
+ performPhantomRemoval(dfg);
performIntegerCheckCombining(dfg);
- performCSE(dfg);
+ performGlobalCSE(dfg);
// At this point we're not allowed to do any further code motion because our reasoning
// about code motion assumes that it's OK to insert GC points in random places.
dfg.m_fixpointState = FixpointConverged;
performStoreBarrierElision(dfg);
+ performPhantomRemoval(dfg);
performLivenessAnalysis(dfg);
performCFA(dfg);
if (Options::validateFTLOSRExitLiveness())
diff --git a/Source/JavaScriptCore/dfg/DFGPureValue.cpp b/Source/JavaScriptCore/dfg/DFGPureValue.cpp
new file mode 100644
index 0000000..4c9f60c
--- /dev/null
+++ b/Source/JavaScriptCore/dfg/DFGPureValue.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 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 "DFGPureValue.h"
+
+#if ENABLE(DFG_JIT)
+
+#include "DFGGraph.h"
+
+namespace JSC { namespace DFG {
+
+void PureValue::dump(PrintStream& out) const
+{
+ out.print(Graph::opName(op()));
+ out.print("(");
+ CommaPrinter comma;
+ for (unsigned i = 0; i < AdjacencyList::Size; ++i) {
+ if (children().child(i))
+ out.print(comma, children().child(i));
+ }
+ if (m_info)
+ out.print(comma, m_info);
+ out.print(")");
+}
+
+} } // namespace JSC::DFG
+
+#endif // ENABLE(DFG_JIT)
+
diff --git a/Source/JavaScriptCore/dfg/DFGPureValue.h b/Source/JavaScriptCore/dfg/DFGPureValue.h
new file mode 100644
index 0000000..e7d6a3d
--- /dev/null
+++ b/Source/JavaScriptCore/dfg/DFGPureValue.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#ifndef DFGPureValue_h
+#define DFGPureValue_h
+
+#if ENABLE(DFG_JIT)
+
+#include "DFGNode.h"
+
+namespace JSC { namespace DFG {
+
+class PureValue {
+public:
+ PureValue()
+ : m_op(LastNodeType)
+ , m_info(0)
+ {
+ }
+
+ PureValue(NodeType op, const AdjacencyList& children, uintptr_t info)
+ : m_op(op)
+ , m_children(children.sanitized())
+ , m_info(info)
+ {
+ ASSERT(!(defaultFlags(op) & NodeHasVarArgs));
+ }
+
+ PureValue(NodeType op, const AdjacencyList& children, const void* ptr)
+ : PureValue(op, children, bitwise_cast<uintptr_t>(ptr))
+ {
+ }
+
+ PureValue(NodeType op, const AdjacencyList& children)
+ : PureValue(op, children, static_cast<uintptr_t>(0))
+ {
+ }
+
+ PureValue(Node* node, uintptr_t info)
+ : PureValue(node->op(), node->children, info)
+ {
+ }
+
+ PureValue(Node* node, const void* ptr)
+ : PureValue(node->op(), node->children, ptr)
+ {
+ }
+
+ PureValue(Node* node)
+ : PureValue(node->op(), node->children)
+ {
+ }
+
+ PureValue(WTF::HashTableDeletedValueType)
+ : m_op(LastNodeType)
+ , m_info(1)
+ {
+ }
+
+ bool operator!() const { return m_op == LastNodeType && !m_info; }
+
+ NodeType op() const { return m_op; }
+ const AdjacencyList& children() const { return m_children; }
+ uintptr_t info() const { return m_info; }
+
+ unsigned hash() const
+ {
+ return WTF::IntHash<int>::hash(static_cast<int>(m_op)) + m_children.hash() + m_info;
+ }
+
+ bool operator==(const PureValue& other) const
+ {
+ return m_op == other.m_op
+ && m_children == other.m_children
+ && m_info == other.m_info;
+ }
+
+ bool isHashTableDeletedValue() const
+ {
+ return m_op == LastNodeType && m_info;
+ }
+
+ void dump(PrintStream& out) const;
+
+private:
+ NodeType m_op;
+ AdjacencyList m_children;
+ uintptr_t m_info;
+};
+
+struct PureValueHash {
+ static unsigned hash(const PureValue& key) { return key.hash(); }
+ static bool equal(const PureValue& a, const PureValue& b) { return a == b; }
+ static const bool safeToCompareToEmptyOrDeleted = true;
+};
+
+} } // namespace JSC::DFG
+
+namespace WTF {
+
+template<typename T> struct DefaultHash;
+template<> struct DefaultHash<JSC::DFG::PureValue> {
+ typedef JSC::DFG::PureValueHash Hash;
+};
+
+template<typename T> struct HashTraits;
+template<> struct HashTraits<JSC::DFG::PureValue> : SimpleClassHashTraits<JSC::DFG::PureValue> {
+ static const bool emptyValueIsZero = false;
+};
+
+} // namespace WTF
+
+namespace JSC { namespace DFG {
+
+typedef HashMap<PureValue, Node*> PureMap;
+typedef HashMap<PureValue, Vector<Node*>> PureMultiMap;
+
+} } // namespace JSC::DFG
+
+#endif // ENABLE(DFG_JIT)
+
+#endif // DFGPureValue_h
+
diff --git a/Source/JavaScriptCore/dfg/DFGSSAConversionPhase.cpp b/Source/JavaScriptCore/dfg/DFGSSAConversionPhase.cpp
index e194ea8..cd71bc5 100644
--- a/Source/JavaScriptCore/dfg/DFGSSAConversionPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSSAConversionPhase.cpp
@@ -246,7 +246,7 @@
block->variablesAtHead.operand(phi->local()), "\n");
}
ASSERT(phi != block->variablesAtHead.operand(phi->local()));
- phi->misc.replacement = block->variablesAtHead.operand(phi->local());
+ phi->replacement = block->variablesAtHead.operand(phi->local());
}
}
@@ -261,9 +261,9 @@
Node* node = block->variablesAtHead[i];
if (!node)
continue;
- while (node->misc.replacement) {
- ASSERT(node != node->misc.replacement);
- node = node->misc.replacement;
+ while (node->replacement) {
+ ASSERT(node != node->replacement);
+ node = node->replacement;
}
block->variablesAtHead[i] = node;
}
@@ -301,11 +301,11 @@
continue;
for (unsigned phiIndex = block->phis.size(); phiIndex--;) {
- block->phis[phiIndex]->misc.replacement =
+ block->phis[phiIndex]->replacement =
block->variablesAtHead.operand(block->phis[phiIndex]->local());
}
for (unsigned nodeIndex = block->size(); nodeIndex--;)
- ASSERT(!block->at(nodeIndex)->misc.replacement);
+ ASSERT(!block->at(nodeIndex)->replacement);
for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
Node* node = block->at(nodeIndex);
@@ -319,7 +319,7 @@
node->mergeFlags(NodeMustGenerate);
else
node->setOpAndDefaultFlags(Check);
- node->misc.replacement = node->child1().node(); // Only for Upsilons.
+ node->replacement = node->child1().node(); // Only for Upsilons.
break;
}
@@ -333,7 +333,7 @@
if (variable->isCaptured())
break;
node->convertToPhantom();
- node->misc.replacement = block->variablesAtHead.operand(variable->local());
+ node->replacement = block->variablesAtHead.operand(variable->local());
break;
}
@@ -342,7 +342,7 @@
node->convertToPhantom();
// This is only for Upsilons. An Upsilon will only refer to a Flush if
// there were no SetLocals or GetLocals in the block.
- node->misc.replacement = block->variablesAtHead.operand(node->local());
+ node->replacement = block->variablesAtHead.operand(node->local());
break;
}
@@ -367,7 +367,7 @@
node->convertToPhantom();
// This is only for Upsilons. An Upsilon will only refer to a
// PhantomLocal if there were no SetLocals or GetLocals in the block.
- node->misc.replacement = block->variablesAtHead.operand(variable->local());
+ node->replacement = block->variablesAtHead.operand(variable->local());
break;
}
@@ -398,7 +398,7 @@
block->variablesAtTail.clear();
block->valuesAtHead.clear();
block->valuesAtHead.clear();
- block->ssa = adoptPtr(new BasicBlock::SSAData(block));
+ block->ssa = std::make_unique<BasicBlock::SSAData>(block);
}
m_graph.m_arguments.clear();
diff --git a/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp b/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp
index f0c09f1..993c7e6 100644
--- a/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -28,6 +28,8 @@
#if ENABLE(DFG_JIT)
+#include "DFGAbstractHeap.h"
+#include "DFGClobberize.h"
#include "DFGGraph.h"
#include "DFGInsertionSet.h"
#include "DFGPhase.h"
@@ -216,6 +218,76 @@
break;
}
+ case Flush: {
+ ASSERT(m_graph.m_form != SSA);
+
+ Node* setLocal = nullptr;
+ VirtualRegister local = m_node->local();
+
+ if (m_node->variableAccessData()->isCaptured()) {
+ for (unsigned i = m_nodeIndex; i--;) {
+ Node* node = m_block->at(i);
+ bool done = false;
+ switch (node->op()) {
+ case GetLocal:
+ case Flush:
+ if (node->local() == local)
+ done = true;
+ break;
+
+ case GetLocalUnlinked:
+ if (node->unlinkedLocal() == local)
+ done = true;
+ break;
+
+ case SetLocal: {
+ if (node->local() != local)
+ break;
+ setLocal = node;
+ done = true;
+ break;
+ }
+
+ case Phantom:
+ case Check:
+ case HardPhantom:
+ case MovHint:
+ case JSConstant:
+ case DoubleConstant:
+ case Int52Constant:
+ break;
+
+ default:
+ done = true;
+ break;
+ }
+ if (done)
+ break;
+ }
+ } else {
+ for (unsigned i = m_nodeIndex; i--;) {
+ Node* node = m_block->at(i);
+ if (node->op() == SetLocal && node->local() == local) {
+ setLocal = node;
+ break;
+ }
+ if (accessesOverlap(m_graph, node, AbstractHeap(Variables, local)))
+ break;
+ }
+ }
+
+ if (!setLocal)
+ break;
+
+ m_node->convertToPhantom();
+ Node* dataNode = setLocal->child1().node();
+ DFG_ASSERT(m_graph, m_node, dataNode->hasResult());
+ m_node->child1() = dataNode->defaultEdge();
+ m_graph.dethread();
+ m_changed = true;
+ break;
+ }
+
default:
break;
}
diff --git a/Source/JavaScriptCore/dfg/DFGWatchableStructureWatchingPhase.cpp b/Source/JavaScriptCore/dfg/DFGWatchableStructureWatchingPhase.cpp
index a526fad..3781e5e 100644
--- a/Source/JavaScriptCore/dfg/DFGWatchableStructureWatchingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGWatchableStructureWatchingPhase.cpp
@@ -86,7 +86,6 @@
case MultiGetByOffset:
for (unsigned i = node->multiGetByOffsetData().variants.size(); i--;) {
GetByIdVariant& variant = node->multiGetByOffsetData().variants[i];
- tryWatch(m_graph.freeze(variant.specificValue())->structure());
tryWatch(variant.structureSet());
// Don't need to watch anything in the structure chain because that would
// have been decomposed into CheckStructure's. Don't need to watch the