summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/bytecode/Watchpoint.h
diff options
context:
space:
mode:
Diffstat (limited to 'Source/JavaScriptCore/bytecode/Watchpoint.h')
-rw-r--r--Source/JavaScriptCore/bytecode/Watchpoint.h313
1 files changed, 275 insertions, 38 deletions
diff --git a/Source/JavaScriptCore/bytecode/Watchpoint.h b/Source/JavaScriptCore/bytecode/Watchpoint.h
index e6fba93a9..869e908c8 100644
--- a/Source/JavaScriptCore/bytecode/Watchpoint.h
+++ b/Source/JavaScriptCore/bytecode/Watchpoint.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 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
@@ -26,12 +26,48 @@
#ifndef Watchpoint_h
#define Watchpoint_h
-#include <wtf/RefCounted.h>
+#include <wtf/Atomics.h>
+#include <wtf/FastMalloc.h>
+#include <wtf/Noncopyable.h>
+#include <wtf/PrintStream.h>
#include <wtf/SentinelLinkedList.h>
+#include <wtf/ThreadSafeRefCounted.h>
namespace JSC {
+class FireDetail {
+ void* operator new(size_t) = delete;
+
+public:
+ FireDetail()
+ {
+ }
+
+ virtual ~FireDetail()
+ {
+ }
+
+ virtual void dump(PrintStream&) const = 0;
+};
+
+class StringFireDetail : public FireDetail {
+public:
+ StringFireDetail(const char* string)
+ : m_string(string)
+ {
+ }
+
+ virtual void dump(PrintStream& out) const override;
+
+private:
+ const char* m_string;
+};
+
+class WatchpointSet;
+
class Watchpoint : public BasicRawSentinelNode<Watchpoint> {
+ WTF_MAKE_NONCOPYABLE(Watchpoint);
+ WTF_MAKE_FAST_ALLOCATED;
public:
Watchpoint()
{
@@ -39,23 +75,59 @@ public:
virtual ~Watchpoint();
- void fire() { fireInternal(); }
-
protected:
- virtual void fireInternal() = 0;
+ virtual void fireInternal(const FireDetail&) = 0;
+
+private:
+ friend class WatchpointSet;
+ void fire(const FireDetail&);
};
-enum InitialWatchpointSetMode { InitializedWatching, InitializedBlind };
+enum WatchpointState {
+ ClearWatchpoint,
+ IsWatched,
+ IsInvalidated
+};
class InlineWatchpointSet;
-class WatchpointSet : public RefCounted<WatchpointSet> {
+class WatchpointSet : public ThreadSafeRefCounted<WatchpointSet> {
+ friend class LLIntOffsetsExtractor;
public:
- WatchpointSet(InitialWatchpointSetMode);
- ~WatchpointSet();
+ JS_EXPORT_PRIVATE WatchpointSet(WatchpointState);
+ JS_EXPORT_PRIVATE ~WatchpointSet(); // Note that this will not fire any of the watchpoints; if you need to know when a WatchpointSet dies then you need a separate mechanism for this.
- bool isStillValid() const { return !m_isInvalidated; }
- bool hasBeenInvalidated() const { return m_isInvalidated; }
+ // Fast way of getting the state, which only works from the main thread.
+ WatchpointState stateOnJSThread() const
+ {
+ return static_cast<WatchpointState>(m_state);
+ }
+
+ // It is safe to call this from another thread. It may return an old
+ // state. Guarantees that if *first* read the state() of the thing being
+ // watched and it returned IsWatched and *second* you actually read its
+ // value then it's safe to assume that if the state being watched changes
+ // then also the watchpoint state() will change to IsInvalidated.
+ WatchpointState state() const
+ {
+ WTF::loadLoadFence();
+ WatchpointState result = static_cast<WatchpointState>(m_state);
+ WTF::loadLoadFence();
+ return result;
+ }
+
+ // It is safe to call this from another thread. It may return true
+ // even if the set actually had been invalidated, but that ought to happen
+ // only in the case of races, and should be rare. Guarantees that if you
+ // call this after observing something that must imply that the set is
+ // invalidated, then you will see this return false. This is ensured by
+ // issuing a load-load fence prior to querying the state.
+ bool isStillValid() const
+ {
+ return state() != IsInvalidated;
+ }
+ // Like isStillValid(), may be called from another thread.
+ bool hasBeenInvalidated() const { return !isStillValid(); }
// As a convenience, this will ignore 0. That's because code paths in the DFG
// that create speculation watchpoints may choose to bail out if speculation
@@ -67,27 +139,76 @@ public:
// watchpoint would have fired. That's a pretty good indication that you
// probably don't want to set watchpoints, since we typically don't want to
// set watchpoints that we believe will actually be fired.
- void startWatching() { m_isWatched = true; }
+ void startWatching()
+ {
+ ASSERT(m_state != IsInvalidated);
+ if (m_state == IsWatched)
+ return;
+ WTF::storeStoreFence();
+ m_state = IsWatched;
+ WTF::storeStoreFence();
+ }
- void notifyWrite()
+ void fireAll(const FireDetail& detail)
{
- if (!m_isWatched)
+ if (LIKELY(m_state != IsWatched))
return;
- notifyWriteSlow();
+ fireAllSlow(detail);
+ }
+
+ void fireAll(const char* reason)
+ {
+ if (LIKELY(m_state != IsWatched))
+ return;
+ fireAllSlow(reason);
+ }
+
+ void touch(const FireDetail& detail)
+ {
+ if (state() == ClearWatchpoint)
+ startWatching();
+ else
+ fireAll(detail);
+ }
+
+ void touch(const char* reason)
+ {
+ touch(StringFireDetail(reason));
+ }
+
+ void invalidate(const FireDetail& detail)
+ {
+ if (state() == IsWatched)
+ fireAll(detail);
+ m_state = IsInvalidated;
+ }
+
+ void invalidate(const char* reason)
+ {
+ invalidate(StringFireDetail(reason));
+ }
+
+ bool isBeingWatched() const
+ {
+ return m_setIsNotEmpty;
}
- bool* addressOfIsWatched() { return &m_isWatched; }
+ int8_t* addressOfState() { return &m_state; }
+ static ptrdiff_t offsetOfState() { return OBJECT_OFFSETOF(WatchpointSet, m_state); }
+ int8_t* addressOfSetIsNotEmpty() { return &m_setIsNotEmpty; }
- JS_EXPORT_PRIVATE void notifyWriteSlow(); // Call only if you've checked isWatched.
+ JS_EXPORT_PRIVATE void fireAllSlow(const FireDetail&); // Call only if you've checked isWatched.
+ JS_EXPORT_PRIVATE void fireAllSlow(const char* reason); // Ditto.
private:
- void fireAllWatchpoints();
+ void fireAllWatchpoints(const FireDetail&);
friend class InlineWatchpointSet;
-
- SentinelLinkedList<Watchpoint, BasicRawSentinelNode<Watchpoint> > m_set;
- bool m_isWatched;
- bool m_isInvalidated;
+
+ int8_t m_state;
+ int8_t m_setIsNotEmpty;
+
+ SentinelLinkedList<Watchpoint, BasicRawSentinelNode<Watchpoint>> m_set;
};
// InlineWatchpointSet is a low-overhead, non-copyable watchpoint set in which
@@ -112,8 +233,8 @@ private:
class InlineWatchpointSet {
WTF_MAKE_NONCOPYABLE(InlineWatchpointSet);
public:
- InlineWatchpointSet(InitialWatchpointSetMode mode)
- : m_data((mode == InitializedWatching ? IsWatchedFlag : 0) | IsThinFlag)
+ InlineWatchpointSet(WatchpointState state)
+ : m_data(encodeState(state))
{
}
@@ -124,13 +245,37 @@ public:
freeFat();
}
+ // Fast way of getting the state, which only works from the main thread.
+ WatchpointState stateOnJSThread() const
+ {
+ uintptr_t data = m_data;
+ if (isFat(data))
+ return fat(data)->stateOnJSThread();
+ return decodeState(data);
+ }
+
+ // It is safe to call this from another thread. It may return a prior state,
+ // but that should be fine since you should only perform actions based on the
+ // state if you also add a watchpoint.
+ WatchpointState state() const
+ {
+ WTF::loadLoadFence();
+ uintptr_t data = m_data;
+ WTF::loadLoadFence();
+ if (isFat(data))
+ return fat(data)->state();
+ return decodeState(data);
+ }
+
+ // It is safe to call this from another thread. It may return false
+ // even if the set actually had been invalidated, but that ought to happen
+ // only in the case of races, and should be rare.
bool hasBeenInvalidated() const
{
- if (isFat())
- return fat()->hasBeenInvalidated();
- return m_data & IsInvalidatedFlag;
+ return state() == IsInvalidated;
}
+ // Like hasBeenInvalidated(), may be called from another thread.
bool isStillValid() const
{
return !hasBeenInvalidated();
@@ -144,38 +289,130 @@ public:
fat()->startWatching();
return;
}
- m_data |= IsWatchedFlag;
+ ASSERT(decodeState(m_data) != IsInvalidated);
+ m_data = encodeState(IsWatched);
}
- void notifyWrite()
+ void fireAll(const FireDetail& detail)
{
if (isFat()) {
- fat()->notifyWrite();
+ fat()->fireAll(detail);
return;
}
- if (!(m_data & IsWatchedFlag))
+ if (decodeState(m_data) == ClearWatchpoint)
return;
- m_data |= IsInvalidatedFlag;
+ m_data = encodeState(IsInvalidated);
+ WTF::storeStoreFence();
+ }
+
+ void invalidate(const FireDetail& detail)
+ {
+ if (isFat())
+ fat()->invalidate(detail);
+ else
+ m_data = encodeState(IsInvalidated);
+ }
+
+ JS_EXPORT_PRIVATE void fireAll(const char* reason);
+
+ void touch(const FireDetail& detail)
+ {
+ if (isFat()) {
+ fat()->touch(detail);
+ return;
+ }
+ uintptr_t data = m_data;
+ if (decodeState(data) == IsInvalidated)
+ return;
+ WTF::storeStoreFence();
+ if (decodeState(data) == ClearWatchpoint)
+ m_data = encodeState(IsWatched);
+ else
+ m_data = encodeState(IsInvalidated);
+ WTF::storeStoreFence();
+ }
+
+ void touch(const char* reason)
+ {
+ touch(StringFireDetail(reason));
+ }
+
+ // Note that for any watchpoint that is visible from the DFG, it would be incorrect to write code like:
+ //
+ // if (w.isBeingWatched())
+ // w.fireAll()
+ //
+ // Concurrently to this, the DFG could do:
+ //
+ // if (w.isStillValid())
+ // perform optimizations;
+ // if (!w.isStillValid())
+ // retry compilation;
+ //
+ // Note that the DFG algorithm is widespread, and sound, because fireAll() and invalidate() will leave
+ // the watchpoint in a !isStillValid() state. Hence, if fireAll() or invalidate() interleaved between
+ // the first isStillValid() check and the second one, then it would simply cause the DFG to retry
+ // compilation later.
+ //
+ // But, if you change some piece of state that the DFG might optimize for, but invalidate the
+ // watchpoint by doing:
+ //
+ // if (w.isBeingWatched())
+ // w.fireAll()
+ //
+ // then the DFG would never know that you invalidated state between the two checks.
+ //
+ // There are two ways to work around this:
+ //
+ // - Call fireAll() without a isBeingWatched() check. Then, the DFG will know that the watchpoint has
+ // been invalidated when it does its second check.
+ //
+ // - Do not expose the watchpoint set to the DFG directly, and have your own way of validating whether
+ // the assumptions that the DFG thread used are still valid when the DFG code is installed.
+ bool isBeingWatched() const
+ {
+ if (isFat())
+ return fat()->isBeingWatched();
+ return false;
}
private:
static const uintptr_t IsThinFlag = 1;
- static const uintptr_t IsInvalidatedFlag = 2;
- static const uintptr_t IsWatchedFlag = 4;
+ static const uintptr_t StateMask = 6;
+ static const uintptr_t StateShift = 1;
+
+ static bool isThin(uintptr_t data) { return data & IsThinFlag; }
+ static bool isFat(uintptr_t data) { return !isThin(data); }
+
+ static WatchpointState decodeState(uintptr_t data)
+ {
+ ASSERT(isThin(data));
+ return static_cast<WatchpointState>((data & StateMask) >> StateShift);
+ }
- bool isThin() const { return m_data & IsThinFlag; }
- bool isFat() const { return !isThin(); };
+ static uintptr_t encodeState(WatchpointState state)
+ {
+ return (static_cast<uintptr_t>(state) << StateShift) | IsThinFlag;
+ }
+
+ bool isThin() const { return isThin(m_data); }
+ bool isFat() const { return isFat(m_data); };
+
+ static WatchpointSet* fat(uintptr_t data)
+ {
+ return bitwise_cast<WatchpointSet*>(data);
+ }
WatchpointSet* fat()
{
ASSERT(isFat());
- return bitwise_cast<WatchpointSet*>(m_data);
+ return fat(m_data);
}
const WatchpointSet* fat() const
{
ASSERT(isFat());
- return bitwise_cast<WatchpointSet*>(m_data);
+ return fat(m_data);
}
WatchpointSet* inflate()