Bug 892643: Implement PersistentRooted<T>, an unrestricted-lifetime rooting type. r=terrence, jcoppeard

This commit is contained in:
Jim Blandy 2013-11-04 13:35:08 -08:00
parent f204ac27b3
commit 1dfa8c46d0
9 changed files with 438 additions and 7 deletions

View File

@ -8,6 +8,7 @@
#define js_RootingAPI_h
#include "mozilla/GuardObjects.h"
#include "mozilla/LinkedList.h"
#include "mozilla/NullPtr.h"
#include "mozilla/TypeTraits.h"
@ -143,6 +144,7 @@ struct Cell;
namespace JS {
template <typename T> class Rooted;
template <typename T> class PersistentRooted;
/* This is exposing internal state of the GC for inlining purposes. */
JS_FRIEND_API(bool) isGCEnabled();
@ -440,6 +442,11 @@ class MOZ_NONHEAP_CLASS Handle : public js::HandleBase<T>
Handle(const Rooted<S> &root,
typename mozilla::EnableIf<mozilla::IsConvertible<S, T>::value, int>::Type dummy = 0);
template <typename S>
inline
Handle(const PersistentRooted<S> &root,
typename mozilla::EnableIf<mozilla::IsConvertible<S, T>::value, int>::Type dummy = 0);
/* Construct a read only handle from a mutable handle. */
template <typename S>
inline
@ -484,6 +491,7 @@ class MOZ_STACK_CLASS MutableHandle : public js::MutableHandleBase<T>
{
public:
inline MutableHandle(Rooted<T> *root);
inline MutableHandle(PersistentRooted<T> *root);
MutableHandle(int) MOZ_DELETE;
#ifdef MOZ_HAVE_CXX11_NULLPTR
MutableHandle(decltype(nullptr)) MOZ_DELETE;
@ -797,13 +805,6 @@ template <>
class Rooted<JSStableString *>;
#endif
typedef Rooted<JSObject*> RootedObject;
typedef Rooted<JSFunction*> RootedFunction;
typedef Rooted<JSScript*> RootedScript;
typedef Rooted<JSString*> RootedString;
typedef Rooted<jsid> RootedId;
typedef Rooted<JS::Value> RootedValue;
} /* namespace JS */
namespace js {
@ -1024,6 +1025,14 @@ Handle<T>::Handle(const Rooted<S> &root,
ptr = reinterpret_cast<const T *>(root.address());
}
template <typename T> template <typename S>
inline
Handle<T>::Handle(const PersistentRooted<S> &root,
typename mozilla::EnableIf<mozilla::IsConvertible<S, T>::value, int>::Type dummy)
{
ptr = reinterpret_cast<const T *>(root.address());
}
template <typename T> template <typename S>
inline
Handle<T>::Handle(MutableHandle<S> &root,
@ -1041,6 +1050,123 @@ MutableHandle<T>::MutableHandle(Rooted<T> *root)
ptr = root->address();
}
template <typename T>
inline
MutableHandle<T>::MutableHandle(PersistentRooted<T> *root)
{
static_assert(sizeof(MutableHandle<T>) == sizeof(T *),
"MutableHandle must be binary compatible with T*.");
ptr = root->address();
}
/*
* A copyable, assignable global GC root type with arbitrary lifetime, an
* infallible constructor, and automatic unrooting on destruction.
*
* These roots can be used in heap-allocated data structures, so they are not
* associated with any particular JSContext or stack. They are registered with
* the JSRuntime itself, without locking, so they require a full JSContext to be
* constructed, not one of its more restricted superclasses.
*
* Note that you must not use an PersistentRooted in an object owned by a JS
* object:
*
* Whenever one object whose lifetime is decided by the GC refers to another
* such object, that edge must be traced only if the owning JS object is traced.
* This applies not only to JS objects (which obviously are managed by the GC)
* but also to C++ objects owned by JS objects.
*
* If you put a PersistentRooted in such a C++ object, that is almost certainly
* a leak. When a GC begins, the referent of the PersistentRooted is treated as
* live, unconditionally (because a PersistentRooted is a *root*), even if the
* JS object that owns it is unreachable. If there is any path from that
* referent back to the JS object, then the C++ object containing the
* PersistentRooted will not be destructed, and the whole blob of objects will
* not be freed, even if there are no references to them from the outside.
*
* In the context of Firefox, this is a severe restriction: almost everything in
* Firefox is owned by some JS object or another, so using PersistentRooted in
* such objects would introduce leaks. For these kinds of edges, Heap<T> or
* TenuredHeap<T> would be better types. It's up to the implementor of the type
* containing Heap<T> or TenuredHeap<T> members to make sure their referents get
* marked when the object itself is marked.
*/
template<typename T>
class PersistentRooted : public mozilla::LinkedListElement<PersistentRooted<T> > {
typedef mozilla::LinkedList<PersistentRooted> List;
typedef mozilla::LinkedListElement<PersistentRooted> Element;
void registerWithRuntime(JSRuntime *rt) {
JS::shadow::Runtime *srt = JS::shadow::Runtime::asShadowRuntime(rt);
srt->getPersistentRootedList<T>().insertBack(this);
}
public:
PersistentRooted(JSContext *cx) : ptr(js::GCMethods<T>::initial())
{
registerWithRuntime(js::GetRuntime(cx));
}
PersistentRooted(JSContext *cx, T initial) : ptr(initial)
{
registerWithRuntime(js::GetRuntime(cx));
}
PersistentRooted(JSRuntime *rt) : ptr(js::GCMethods<T>::initial())
{
registerWithRuntime(rt);
}
PersistentRooted(JSRuntime *rt, T initial) : ptr(initial)
{
registerWithRuntime(rt);
}
PersistentRooted(PersistentRooted &rhs) : ptr(rhs.ptr)
{
/*
* Copy construction takes advantage of the fact that the original
* is already inserted, and simply adds itself to whatever list the
* original was on - no JSRuntime pointer needed.
*/
rhs.setNext(this);
}
/*
* Important: Return a reference here so passing a Rooted<T> to
* something that takes a |const T&| is not a GC hazard.
*/
operator const T&() const { return ptr; }
T operator->() const { return ptr; }
T *address() { return &ptr; }
const T *address() const { return &ptr; }
T &get() { return ptr; }
const T &get() const { return ptr; }
T &operator=(T value) {
JS_ASSERT(!js::GCMethods<T>::poisoned(value));
ptr = value;
return ptr;
}
T &operator=(const PersistentRooted &value) {
ptr = value;
return ptr;
}
void set(T value) {
JS_ASSERT(!js::GCMethods<T>::poisoned(value));
ptr = value;
}
bool operator!=(const T &other) const { return ptr != other; }
bool operator==(const T &other) const { return ptr == other; }
private:
T ptr;
};
} /* namespace JS */
namespace js {

View File

@ -57,6 +57,8 @@ namespace JS {
class Value;
template <typename T> class Handle;
template <typename T> class MutableHandle;
template <typename T> class Rooted;
template <typename T> class PersistentRooted;
typedef Handle<JSFunction*> HandleFunction;
typedef Handle<jsid> HandleId;
@ -72,6 +74,20 @@ typedef MutableHandle<JSScript*> MutableHandleScript;
typedef MutableHandle<JSString*> MutableHandleString;
typedef MutableHandle<Value> MutableHandleValue;
typedef Rooted<JSObject*> RootedObject;
typedef Rooted<JSFunction*> RootedFunction;
typedef Rooted<JSScript*> RootedScript;
typedef Rooted<JSString*> RootedString;
typedef Rooted<jsid> RootedId;
typedef Rooted<JS::Value> RootedValue;
typedef PersistentRooted<JSFunction*> PersistentRootedFunction;
typedef PersistentRooted<jsid> PersistentRootedId;
typedef PersistentRooted<JSObject*> PersistentRootedObject;
typedef PersistentRooted<JSScript*> PersistentRootedScript;
typedef PersistentRooted<JSString*> PersistentRootedString;
typedef PersistentRooted<Value> PersistentRootedValue;
} // namespace JS
#endif /* js_TypeDecls_h */

View File

@ -94,6 +94,14 @@ using JS::RootedScript;
using JS::RootedString;
using JS::RootedValue;
using JS::PersistentRooted;
using JS::PersistentRootedFunction;
using JS::PersistentRootedId;
using JS::PersistentRootedObject;
using JS::PersistentRootedScript;
using JS::PersistentRootedString;
using JS::PersistentRootedValue;
using JS::Handle;
using JS::HandleFunction;
using JS::HandleId;

View File

@ -14,6 +14,9 @@
namespace js {
namespace gc {
void
MarkPersistentRootedChains(JSTracer *trc);
void
MarkRuntime(JSTracer *trc, bool useSavedRoots = false);

View File

@ -617,6 +617,44 @@ JSPropertyDescriptor::trace(JSTracer *trc)
}
}
// Mark a chain of PersistentRooted pointers that might be null.
template<typename Referent>
static void
MarkPersistentRootedChain(JSTracer *trc,
mozilla::LinkedList<PersistentRooted<Referent *> > &list,
void (*marker)(JSTracer *trc, Referent **ref, const char *name),
const char *name)
{
for (PersistentRooted<Referent *> *r = list.getFirst();
r != nullptr;
r = r->getNext())
{
if (r->get())
marker(trc, r->address(), name);
}
}
void
js::gc::MarkPersistentRootedChains(JSTracer *trc)
{
JSRuntime *rt = trc->runtime;
MarkPersistentRootedChain(trc, rt->functionPersistentRooteds, &MarkObjectRoot,
"PersistentRooted<JSFunction *>");
MarkPersistentRootedChain(trc, rt->objectPersistentRooteds, &MarkObjectRoot,
"PersistentRooted<JSObject *>");
MarkPersistentRootedChain(trc, rt->scriptPersistentRooteds, &MarkScriptRoot,
"PersistentRooted<JSScript *>");
MarkPersistentRootedChain(trc, rt->stringPersistentRooteds, &MarkStringRoot,
"PersistentRooted<JSString *>");
// Mark the PersistentRooted chains of types that are never null.
for (JS::PersistentRootedId *r = rt->idPersistentRooteds.getFirst(); r != nullptr; r = r->getNext())
MarkIdRoot(trc, r->address(), "PersistentRooted<jsid>");
for (JS::PersistentRootedValue *r = rt->valuePersistentRooteds.getFirst(); r != nullptr; r = r->getNext())
MarkValueRoot(trc, r->address(), "PersistentRooted<Value>");
}
void
js::gc::MarkRuntime(JSTracer *trc, bool useSavedRoots)
{
@ -661,6 +699,8 @@ js::gc::MarkRuntime(JSTracer *trc, bool useSavedRoots)
}
}
MarkPersistentRootedChains(trc);
if (rt->scriptAndCountsVector) {
ScriptAndCountsVector &vec = *rt->scriptAndCountsVector;
for (size_t i = 0; i < vec.length(); i++)

View File

@ -50,6 +50,7 @@ SOURCES += [
'testOps.cpp',
'testOriginPrincipals.cpp',
'testParseJSON.cpp',
'testPersistentRooted.cpp',
'testProfileStrings.cpp',
'testPropCache.cpp',
'testRegExp.cpp',

View File

@ -0,0 +1,185 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#if defined(JSGC_USE_EXACT_ROOTING)
#include "js/Class.h"
#include "jsapi-tests/tests.h"
using namespace JS;
struct BarkWhenTracedClass {
static int finalizeCount;
static int traceCount;
static const JSClass class_;
static void finalize(JSFreeOp *fop, JSObject *obj) { finalizeCount++; }
static void trace(JSTracer *trc, JSObject *obj) { traceCount++; }
static void reset() { finalizeCount = 0; traceCount = 0; }
};
int BarkWhenTracedClass::finalizeCount;
int BarkWhenTracedClass::traceCount;
const JSClass BarkWhenTracedClass::class_ = {
"BarkWhenTracedClass", 0,
JS_PropertyStub,
JS_DeletePropertyStub,
JS_PropertyStub,
JS_StrictPropertyStub,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
finalize,
nullptr,
nullptr,
nullptr,
nullptr,
trace
};
struct Kennel {
PersistentRootedObject obj;
Kennel(JSContext *cx) : obj(cx) { }
Kennel(JSContext *cx, const HandleObject &woof) : obj(cx, woof) { };
};
// A function for allocating a Kennel and a barker. Only allocating
// PersistentRooteds on the heap, and in this function, helps ensure that the
// conservative GC doesn't find stray references to the barker. Ugh.
JS_NEVER_INLINE static Kennel *
Allocate(JSContext *cx)
{
RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_, nullptr, nullptr));
if (!barker)
return nullptr;
return new Kennel(cx, barker);
}
// Do a GC, expecting |n| barkers to be finalized.
static bool
GCFinalizesNBarkers(JSContext *cx, int n)
{
int preGCTrace = BarkWhenTracedClass::traceCount;
int preGCFinalize = BarkWhenTracedClass::finalizeCount;
JS_GC(JS_GetRuntime(cx));
return (BarkWhenTracedClass::finalizeCount == preGCFinalize + n &&
BarkWhenTracedClass::traceCount > preGCTrace);
}
// PersistentRooted instances protect their contents from being recycled.
BEGIN_TEST(test_PersistentRooted)
{
BarkWhenTracedClass::reset();
Kennel *kennel = Allocate(cx);
CHECK(kennel);
// GC should be able to find our barker.
CHECK(GCFinalizesNBarkers(cx, 0));
delete(kennel);
// Now GC should not be able to find the barker.
JS_GC(JS_GetRuntime(cx));
CHECK(BarkWhenTracedClass::finalizeCount == 1);
return true;
}
END_TEST(test_PersistentRooted)
// GC should not be upset by null PersistentRooteds.
BEGIN_TEST(test_PersistentRootedNull)
{
BarkWhenTracedClass::reset();
Kennel kennel(cx);
CHECK(!kennel.obj);
JS_GC(JS_GetRuntime(cx));
CHECK(BarkWhenTracedClass::finalizeCount == 0);
return true;
}
END_TEST(test_PersistentRootedNull)
// Copy construction works.
BEGIN_TEST(test_PersistentRootedCopy)
{
BarkWhenTracedClass::reset();
Kennel *kennel = Allocate(cx);
CHECK(kennel);
CHECK(GCFinalizesNBarkers(cx, 0));
// Copy construction! AMAZING!
Kennel *newKennel = new Kennel(*kennel);
CHECK(GCFinalizesNBarkers(cx, 0));
delete(kennel);
CHECK(GCFinalizesNBarkers(cx, 0));
delete(newKennel);
// Now that kennel and nowKennel are both deallocated, GC should not be
// able to find the barker.
JS_GC(JS_GetRuntime(cx));
CHECK(BarkWhenTracedClass::finalizeCount == 1);
return true;
}
END_TEST(test_PersistentRootedCopy)
// Assignment works.
BEGIN_TEST(test_PersistentRootedAssign)
{
BarkWhenTracedClass::reset();
Kennel *kennel = Allocate(cx);
CHECK(kennel);
CHECK(GCFinalizesNBarkers(cx, 0));
// Allocate a new, empty kennel.
Kennel *kennel2 = new Kennel(cx);
// Assignment! ASTONISHING!
*kennel2 = *kennel;
// With both kennels referring to the same barker, it is held alive.
CHECK(GCFinalizesNBarkers(cx, 0));
delete(kennel2);
// The destination of the assignment alone holds the barker alive.
CHECK(GCFinalizesNBarkers(cx, 0));
// Allocate a second barker.
kennel2 = Allocate(cx);
CHECK(kennel);
*kennel = *kennel2;
// Nothing refers to the first kennel any more.
CHECK(GCFinalizesNBarkers(cx, 1));
delete(kennel);
delete(kennel2);
// Now that kennel and kennel2 are both deallocated, GC should not be
// able to find the barker.
JS_GC(JS_GetRuntime(cx));
CHECK(BarkWhenTracedClass::finalizeCount == 2);
return true;
}
END_TEST(test_PersistentRootedAssign)
#endif // defined(JSGC_USE_EXACT_ROOTING)

View File

@ -1027,6 +1027,13 @@ js_FinishGC(JSRuntime *rt)
rt->gcChunkPool.expireAndFree(rt, true);
rt->gcRootsHash.clear();
rt->functionPersistentRooteds.clear();
rt->idPersistentRooteds.clear();
rt->objectPersistentRooteds.clear();
rt->scriptPersistentRooteds.clear();
rt->stringPersistentRooteds.clear();
rt->valuePersistentRooteds.clear();
}
template <typename T> struct BarrierOwner {};

View File

@ -11,6 +11,7 @@
* JS public API typedefs.
*/
#include "mozilla/LinkedList.h"
#include "mozilla/NullPtr.h"
#include "mozilla/PodOperations.h"
@ -173,9 +174,12 @@ typedef bool (*JSInitCallback)(void);
typedef void
(* JSTraceDataOp)(JSTracer *trc, void *data);
void js_FinishGC(JSRuntime *rt);
namespace js {
namespace gc {
class StoreBuffer;
void MarkPersistentRootedChains(JSTracer *);
}
}
@ -224,8 +228,49 @@ struct Runtime
static JS::shadow::Runtime *asShadowRuntime(JSRuntime *rt) {
return reinterpret_cast<JS::shadow::Runtime*>(rt);
}
/* Allow inlining of PersistentRooted constructors and destructors. */
private:
template <typename Referent> friend class JS::PersistentRooted;
friend void js::gc::MarkPersistentRootedChains(JSTracer *);
friend void ::js_FinishGC(JSRuntime *rt);
mozilla::LinkedList<PersistentRootedFunction> functionPersistentRooteds;
mozilla::LinkedList<PersistentRootedId> idPersistentRooteds;
mozilla::LinkedList<PersistentRootedObject> objectPersistentRooteds;
mozilla::LinkedList<PersistentRootedScript> scriptPersistentRooteds;
mozilla::LinkedList<PersistentRootedString> stringPersistentRooteds;
mozilla::LinkedList<PersistentRootedValue> valuePersistentRooteds;
/* Specializations of this return references to the appropriate list. */
template<typename Referent>
inline mozilla::LinkedList<PersistentRooted<Referent> > &getPersistentRootedList();
};
template<>
inline mozilla::LinkedList<PersistentRootedFunction>
&Runtime::getPersistentRootedList<JSFunction *>() { return functionPersistentRooteds; }
template<>
inline mozilla::LinkedList<PersistentRootedId>
&Runtime::getPersistentRootedList<jsid>() { return idPersistentRooteds; }
template<>
inline mozilla::LinkedList<PersistentRootedObject>
&Runtime::getPersistentRootedList<JSObject *>() { return objectPersistentRooteds; }
template<>
inline mozilla::LinkedList<PersistentRootedScript>
&Runtime::getPersistentRootedList<JSScript *>() { return scriptPersistentRooteds; }
template<>
inline mozilla::LinkedList<PersistentRootedString>
&Runtime::getPersistentRootedList<JSString *>() { return stringPersistentRooteds; }
template<>
inline mozilla::LinkedList<PersistentRootedValue>
&Runtime::getPersistentRootedList<Value>() { return valuePersistentRooteds; }
} /* namespace shadow */
} /* namespace JS */