[JSC] Compress Watchpoint size by using enum type and Packed<> data structure
[WebKit-https.git] / Source / JavaScriptCore / bytecode / Watchpoint.cpp
index 0fe335e..3d70df4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2012-2015 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "config.h"
 #include "Watchpoint.h"
 
-#include "LinkBuffer.h"
+#include "AdaptiveInferredPropertyValueWatchpointBase.h"
+#include "CodeBlockJettisoningWatchpoint.h"
+#include "DFGAdaptiveStructureWatchpoint.h"
+#include "FunctionRareData.h"
+#include "HeapInlines.h"
+#include "LLIntPrototypeLoadAdaptiveStructureWatchpoint.h"
+#include "ObjectToStringAdaptiveStructureWatchpoint.h"
+#include "StructureStubClearingWatchpoint.h"
+#include "VM.h"
 #include <wtf/CompilationThread.h>
-#include <wtf/PassRefPtr.h>
 
 namespace JSC {
 
+void StringFireDetail::dump(PrintStream& out) const
+{
+    out.print(m_string);
+}
+
 Watchpoint::~Watchpoint()
 {
-    if (isOnList())
+    if (isOnList()) {
+        // This will happen if we get destroyed before the set fires. That's totally a valid
+        // possibility. For example:
+        //
+        // CodeBlock has a Watchpoint on transition from structure S1. The transition never
+        // happens, but the CodeBlock gets destroyed because of GC.
         remove();
+    }
+}
+
+void Watchpoint::fire(VM& vm, const FireDetail& detail)
+{
+    RELEASE_ASSERT(!isOnList());
+    switch (m_type) {
+#define JSC_DEFINE_WATCHPOINT_DISPATCH(type, cast) \
+    case Type::type: \
+        static_cast<cast*>(this)->fireInternal(vm, detail); \
+        break;
+    JSC_WATCHPOINT_TYPES(JSC_DEFINE_WATCHPOINT_DISPATCH)
+#undef JSC_DEFINE_WATCHPOINT_DISPATCH
+    }
 }
 
 WatchpointSet::WatchpointSet(WatchpointState state)
     : m_state(state)
+    , m_setIsNotEmpty(false)
 {
 }
 
@@ -60,22 +92,78 @@ void WatchpointSet::add(Watchpoint* watchpoint)
     if (!watchpoint)
         return;
     m_set.push(watchpoint);
+    m_setIsNotEmpty = true;
     m_state = IsWatched;
 }
 
-void WatchpointSet::notifyWriteSlow()
+void WatchpointSet::fireAllSlow(VM& vm, const FireDetail& detail)
 {
     ASSERT(state() == IsWatched);
     
-    fireAllWatchpoints();
-    m_state = IsInvalidated;
+    WTF::storeStoreFence();
+    m_state = IsInvalidated; // Do this first. Needed for adaptive watchpoints.
+    fireAllWatchpoints(vm, detail);
     WTF::storeStoreFence();
 }
 
-void WatchpointSet::fireAllWatchpoints()
+void WatchpointSet::fireAllSlow(VM&, DeferredWatchpointFire* deferredWatchpoints)
 {
-    while (!m_set.isEmpty())
-        m_set.begin()->fire();
+    ASSERT(state() == IsWatched);
+
+    WTF::storeStoreFence();
+    deferredWatchpoints->takeWatchpointsToFire(this);
+    m_state = IsInvalidated; // Do after moving watchpoints to deferredWatchpoints so deferredWatchpoints gets our current state.
+    WTF::storeStoreFence();
+}
+
+void WatchpointSet::fireAllSlow(VM& vm, const char* reason)
+{
+    fireAllSlow(vm, StringFireDetail(reason));
+}
+
+void WatchpointSet::fireAllWatchpoints(VM& vm, const FireDetail& detail)
+{
+    // In case there are any adaptive watchpoints, we need to make sure that they see that this
+    // watchpoint has been already invalidated.
+    RELEASE_ASSERT(hasBeenInvalidated());
+
+    // Firing a watchpoint may cause a GC to happen. This GC could destroy various
+    // Watchpoints themselves while they're in the process of firing. It's not safe
+    // for most Watchpoints to be destructed while they're in the middle of firing.
+    // This GC could also destroy us, and we're not in a safe state to be destroyed.
+    // The safest thing to do is to DeferGCForAWhile to prevent this GC from happening.
+    DeferGCForAWhile deferGC(vm.heap);
+    
+    while (!m_set.isEmpty()) {
+        Watchpoint* watchpoint = m_set.begin();
+        ASSERT(watchpoint->isOnList());
+        
+        // Removing the Watchpoint before firing it makes it possible to implement watchpoints
+        // that add themselves to a different set when they fire. This kind of "adaptive"
+        // watchpoint can be used to track some semantic property that is more fine-graiend than
+        // what the set can convey. For example, we might care if a singleton object ever has a
+        // property called "foo". We can watch for this by checking if its Structure has "foo" and
+        // then watching its transitions. But then the watchpoint fires if any property is added.
+        // So, before the watchpoint decides to invalidate any code, it can check if it is
+        // possible to add itself to the transition watchpoint set of the singleton object's new
+        // Structure.
+        watchpoint->remove();
+        ASSERT(m_set.begin() != watchpoint);
+        ASSERT(!watchpoint->isOnList());
+        
+        watchpoint->fire(vm, detail);
+        // After we fire the watchpoint, the watchpoint pointer may be a dangling pointer. That's
+        // fine, because we have no use for the pointer anymore.
+    }
+}
+
+void WatchpointSet::take(WatchpointSet* other)
+{
+    ASSERT(state() == ClearWatchpoint);
+    m_set.takeFrom(other->m_set);
+    m_setIsNotEmpty = other->m_setIsNotEmpty;
+    m_state = other->m_state;
+    other->m_setIsNotEmpty = false;
 }
 
 void InlineWatchpointSet::add(Watchpoint* watchpoint)
@@ -83,6 +171,11 @@ void InlineWatchpointSet::add(Watchpoint* watchpoint)
     inflate()->add(watchpoint);
 }
 
+void InlineWatchpointSet::fireAll(VM& vm, const char* reason)
+{
+    fireAll(vm, StringFireDetail(reason));
+}
+
 WatchpointSet* InlineWatchpointSet::inflateSlow()
 {
     ASSERT(isThin());
@@ -99,5 +192,48 @@ void InlineWatchpointSet::freeFat()
     fat()->deref();
 }
 
+DeferredWatchpointFire::DeferredWatchpointFire(VM& vm)
+    : m_vm(vm)
+    , m_watchpointsToFire(ClearWatchpoint)
+{
+}
+
+DeferredWatchpointFire::~DeferredWatchpointFire()
+{
+}
+
+void DeferredWatchpointFire::fireAll()
+{
+    if (m_watchpointsToFire.state() == IsWatched)
+        m_watchpointsToFire.fireAll(m_vm, *this);
+}
+
+void DeferredWatchpointFire::takeWatchpointsToFire(WatchpointSet* watchpointsToFire)
+{
+    ASSERT(m_watchpointsToFire.state() == ClearWatchpoint);
+    ASSERT(watchpointsToFire->state() == IsWatched);
+    m_watchpointsToFire.take(watchpointsToFire);
+}
+
 } // namespace JSC
 
+namespace WTF {
+
+void printInternal(PrintStream& out, JSC::WatchpointState state)
+{
+    switch (state) {
+    case JSC::ClearWatchpoint:
+        out.print("ClearWatchpoint");
+        return;
+    case JSC::IsWatched:
+        out.print("IsWatched");
+        return;
+    case JSC::IsInvalidated:
+        out.print("IsInvalidated");
+        return;
+    }
+    RELEASE_ASSERT_NOT_REACHED();
+}
+
+} // namespace WTF
+