Bug 875661 - Part 1: Refactor a thread-safe context out of ForkJoinSlice and JSContext. (r=billm)

This commit is contained in:
Shu-yu Guo 2013-06-20 16:40:53 -07:00
parent 852273e373
commit 7cce974dbd
14 changed files with 142 additions and 94 deletions

View File

@ -20,18 +20,6 @@
using namespace js;
using namespace js::gc;
void *
js::Allocator::onOutOfMemory(void *p, size_t nbytes)
{
return zone->rt->onOutOfMemory(p, nbytes);
}
void
js::Allocator::reportAllocationOverflow()
{
js_ReportAllocationOverflow(NULL);
}
JS::Zone::Zone(JSRuntime *rt)
: rt(rt),
allocator(this),

View File

@ -34,9 +34,9 @@ namespace js {
* parallel or sequential mode, you should make it take an
* |Allocator*| rather than a |JSContext*|.
*/
class Allocator : public MallocProvider<Allocator>
class Allocator
{
JS::Zone *zone;
JS::Zone *zone_;
public:
explicit Allocator(JS::Zone *zone);
@ -44,10 +44,6 @@ class Allocator : public MallocProvider<Allocator>
js::gc::ArenaLists arenas;
inline void *parallelNewGCThing(gc::AllocKind thingKind, size_t thingSize);
void *onOutOfMemory(void *p, size_t nbytes);
inline void updateMallocCounter(size_t nbytes);
void reportAllocationOverflow();
};
typedef Vector<JSCompartment *, 1, SystemAllocPolicy> CompartmentVector;

View File

@ -179,8 +179,7 @@ ion::ParPush(ParPushArgs *args)
// slow path anyhow as it reallocates the elements vector.
ForkJoinSlice *slice = js::ForkJoinSlice::Current();
JSObject::EnsureDenseResult res =
args->object->parExtendDenseElements(slice->allocator,
&args->value, 1);
args->object->parExtendDenseElements(slice, &args->value, 1);
if (res != JSObject::ED_OK)
return NULL;
return args->object;
@ -190,7 +189,7 @@ JSObject *
ion::ParExtendArray(ForkJoinSlice *slice, JSObject *array, uint32_t length)
{
JSObject::EnsureDenseResult res =
array->parExtendDenseElements(slice->allocator, NULL, length);
array->parExtendDenseElements(slice, NULL, length);
if (res != JSObject::ED_OK)
return NULL;
return array;
@ -475,8 +474,7 @@ ion::InitRestParameter(ForkJoinSlice *slice, uint32_t length, Value *rest,
JS_ASSERT(res->type()->unknownProperties());
if (length) {
JSObject::EnsureDenseResult edr =
res->parExtendDenseElements(slice->allocator, rest, length);
JSObject::EnsureDenseResult edr = res->parExtendDenseElements(slice, rest, length);
if (edr != JSObject::ED_OK)
return TP_FATAL;
}

View File

@ -1134,8 +1134,40 @@ js_HandleExecutionInterrupt(JSContext *cx)
return result;
}
JSContext::JSContext(JSRuntime *rt)
js::ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
: ContextFriendFields(rt),
contextKind_(kind),
perThreadData(pt)
{ }
bool
ThreadSafeContext::isJSContext() const
{
return contextKind_ == Context_JS;
}
JSContext *
ThreadSafeContext::asJSContext()
{
JS_ASSERT(isJSContext());
return reinterpret_cast<JSContext *>(this);
}
bool
ThreadSafeContext::isForkJoinSlice() const
{
return contextKind_ == Context_ForkJoin;
}
ForkJoinSlice *
ThreadSafeContext::asForkJoinSlice()
{
JS_ASSERT(isForkJoinSlice());
return reinterpret_cast<ForkJoinSlice *>(this);
}
JSContext::JSContext(JSRuntime *rt)
: ThreadSafeContext(rt, &rt->mainThread, Context_JS),
defaultVersion(JSVERSION_DEFAULT),
hasVersionOverride(false),
throwing(false),

View File

@ -440,16 +440,12 @@ namespace js {
* there is only one instance of this struct, embedded in the
* JSRuntime as the field |mainThread|. During Parallel JS sections,
* however, there will be one instance per worker thread.
*
* The eventual plan is to designate thread-safe portions of the
* interpreter and runtime by having them take |PerThreadData*|
* arguments instead of |JSContext*| or |JSRuntime*|.
*/
class PerThreadData : public js::PerThreadDataFriendFields
{
/*
* Backpointer to the full shared JSRuntime* with which this
* thread is associaed. This is private because accessing the
* thread is associated. This is private because accessing the
* fields of this runtime can provoke race conditions, so the
* intention is that access will be mediated through safe
* functions like |associatedWith()| below.
@ -1502,11 +1498,63 @@ FreeOp::free_(void *p)
js_free(p);
}
class ForkJoinSlice;
/*
* ThreadSafeContext is the base class for both JSContext, the "normal"
* sequential context, and ForkJoinSlice, the per-thread parallel context used
* in PJS.
*
* When cast to a ThreadSafeContext, the only usable operations are casting
* back to the context from which it came, and generic allocation
* operations. These generic versions branch internally based on whether the
* underneath context is really a JSContext or a ForkJoinSlice, and are in
* general more expensive than using the context directly.
*
* Thus, ThreadSafeContext should only be used for VM functions that may be
* called in both sequential and parallel execution. The most likely class of
* VM functions that do these are those that allocate commonly used data
* structures, such as concatenating strings and extending elements.
*/
struct ThreadSafeContext : js::ContextFriendFields,
public MallocProvider<ThreadSafeContext>
{
public:
enum ContextKind {
Context_JS,
Context_ForkJoin
};
private:
ContextKind contextKind_;
public:
PerThreadData *perThreadData;
explicit ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind);
bool isJSContext() const;
JSContext *asJSContext();
bool isForkJoinSlice() const;
ForkJoinSlice *asForkJoinSlice();
void *onOutOfMemory(void *p, size_t nbytes) {
return runtime_->onOutOfMemory(p, nbytes, isJSContext() ? asJSContext() : NULL);
}
inline void updateMallocCounter(size_t nbytes) {
/* Note: this is racy. */
runtime_->updateMallocCounter(zone_, nbytes);
}
void reportAllocationOverflow() {
js_ReportAllocationOverflow(isJSContext() ? asJSContext() : NULL);
}
};
} /* namespace js */
struct JSContext : js::ContextFriendFields,
public mozilla::LinkedListElement<JSContext>,
public js::MallocProvider<JSContext>
struct JSContext : js::ThreadSafeContext,
public mozilla::LinkedListElement<JSContext>
{
explicit JSContext(JSRuntime *rt);
JSContext *thisDuringConstruction() { return this; }

View File

@ -38,16 +38,10 @@ js::AutoCompartment::~AutoCompartment()
cx_->leaveCompartment(origin_);
}
void
js::Allocator::updateMallocCounter(size_t nbytes)
{
zone->rt->updateMallocCounter(zone, nbytes);
}
inline void *
js::Allocator::parallelNewGCThing(gc::AllocKind thingKind, size_t thingSize)
{
return arenas.parallelAllocate(zone, thingKind, thingSize);
return arenas.parallelAllocate(zone_, thingKind, thingSize);
}
namespace js {

View File

@ -1140,7 +1140,7 @@ Zone::reduceGCTriggerBytes(size_t amount)
}
Allocator::Allocator(Zone *zone)
: zone(zone)
: zone_(zone)
{}
inline void

View File

@ -15,6 +15,7 @@
#include "js/RootingAPI.h"
#include "js/TemplateLib.h"
#include "vm/Shape.h"
#include "vm/ForkJoin.h"
namespace js {

View File

@ -2763,41 +2763,35 @@ JSObject::maybeDensifySparseElements(JSContext *cx, HandleObject obj)
}
ObjectElements *
AllocateElements(JSObject::MaybeContext maybecx, JSObject *obj, uint32_t nelems)
AllocateElements(ThreadSafeContext *tcx, JSObject *obj, uint32_t nelems)
{
if (JSContext *cx = maybecx.context) {
#ifdef JSGC_GENERATIONAL
if (tcx->isJSContext()) {
JSContext *cx = tcx->asJSContext();
return cx->runtime()->gcNursery.allocateElements(cx, obj, nelems);
#else
return static_cast<js::ObjectElements *>(cx->malloc_(nelems * sizeof(HeapValue)));
#endif
}
#endif
Allocator *alloc = maybecx.allocator;
return static_cast<js::ObjectElements *>(alloc->malloc_(nelems * sizeof(HeapValue)));
return static_cast<js::ObjectElements *>(tcx->malloc_(nelems * sizeof(HeapValue)));
}
ObjectElements *
ReallocateElements(JSObject::MaybeContext maybecx, JSObject *obj, ObjectElements *oldHeader,
ReallocateElements(ThreadSafeContext *tcx, JSObject *obj, ObjectElements *oldHeader,
uint32_t oldCount, uint32_t newCount)
{
if (JSContext *cx = maybecx.context) {
#ifdef JSGC_GENERATIONAL
if (tcx->isJSContext()) {
JSContext *cx = tcx->asJSContext();
return cx->runtime()->gcNursery.reallocateElements(cx, obj, oldHeader, oldCount, newCount);
#else
return static_cast<js::ObjectElements *>(cx->realloc_(oldHeader,
oldCount * sizeof(HeapValue),
newCount * sizeof(HeapSlot)));
#endif
}
#endif
Allocator *alloc = maybecx.allocator;
return static_cast<js::ObjectElements *>(alloc->realloc_(oldHeader, oldCount * sizeof(HeapSlot),
newCount * sizeof(HeapSlot)));
return static_cast<js::ObjectElements *>(tcx->realloc_(oldHeader, oldCount * sizeof(HeapSlot),
newCount * sizeof(HeapSlot)));
}
bool
JSObject::growElements(MaybeContext cx, uint32_t newcap)
JSObject::growElements(ThreadSafeContext *tcx, uint32_t newcap)
{
JS_ASSERT(isExtensible());
JS_ASSERT_IF(isArray() && !arrayLengthIsWritable(),
@ -2844,11 +2838,11 @@ JSObject::growElements(MaybeContext cx, uint32_t newcap)
ObjectElements *newheader;
if (hasDynamicElements()) {
newheader = ReallocateElements(cx, this, getElementsHeader(), oldAllocated, newAllocated);
newheader = ReallocateElements(tcx, this, getElementsHeader(), oldAllocated, newAllocated);
if (!newheader)
return false; /* Leave elements as its old size. */
} else {
newheader = AllocateElements(cx, this, newAllocated);
newheader = AllocateElements(tcx, this, newAllocated);
if (!newheader)
return false; /* Leave elements as its old size. */
js_memcpy(newheader, getElementsHeader(),

View File

@ -540,17 +540,8 @@ class JSObject : public js::ObjectImpl
static const char *className(JSContext *cx, js::HandleObject obj);
/* Accessors for elements. */
struct MaybeContext {
js::Allocator *allocator;
JSContext *context;
MaybeContext(JSContext *cx) : allocator(NULL), context(cx) {}
MaybeContext(js::Allocator *alloc) : allocator(alloc), context(NULL) {}
};
inline bool ensureElements(JSContext *cx, uint32_t cap);
bool growElements(MaybeContext cx, uint32_t newcap);
bool growElements(js::ThreadSafeContext *tcx, uint32_t newcap);
void shrinkElements(JSContext *cx, uint32_t cap);
inline void setDynamicElements(js::ObjectElements *header);
@ -586,10 +577,10 @@ class JSObject : public js::ObjectImpl
*/
enum EnsureDenseResult { ED_OK, ED_FAILED, ED_SPARSE };
inline EnsureDenseResult ensureDenseElements(JSContext *cx, uint32_t index, uint32_t extra);
inline EnsureDenseResult parExtendDenseElements(js::Allocator *alloc, js::Value *v,
inline EnsureDenseResult parExtendDenseElements(js::ThreadSafeContext *tcx, js::Value *v,
uint32_t extra);
template<typename MallocProviderType>
inline EnsureDenseResult extendDenseElements(MallocProviderType *cx,
inline EnsureDenseResult extendDenseElements(js::ThreadSafeContext *tcx,
uint32_t requiredCapacity, uint32_t extra);
/* Convert a single dense element to a sparse property. */

View File

@ -557,9 +557,8 @@ JSObject::ensureDenseInitializedLength(JSContext *cx, uint32_t index, uint32_t e
}
}
template<typename MallocProviderType>
JSObject::EnsureDenseResult
JSObject::extendDenseElements(MallocProviderType *cx,
JSObject::extendDenseElements(js::ThreadSafeContext *tcx,
uint32_t requiredCapacity, uint32_t extra)
{
/*
@ -589,14 +588,14 @@ JSObject::extendDenseElements(MallocProviderType *cx,
return ED_SPARSE;
}
if (!growElements(cx, requiredCapacity))
if (!growElements(tcx, requiredCapacity))
return ED_FAILED;
return ED_OK;
}
inline JSObject::EnsureDenseResult
JSObject::parExtendDenseElements(js::Allocator *alloc, js::Value *v, uint32_t extra)
JSObject::parExtendDenseElements(js::ThreadSafeContext *tcx, js::Value *v, uint32_t extra)
{
JS_ASSERT(isNative());
JS_ASSERT_IF(isArray(), arrayLengthIsWritable());
@ -608,7 +607,7 @@ JSObject::parExtendDenseElements(js::Allocator *alloc, js::Value *v, uint32_t ex
return ED_SPARSE; /* Overflow. */
if (requiredCapacity > header->capacity) {
EnsureDenseResult edr = extendDenseElements(alloc, requiredCapacity, extra);
EnsureDenseResult edr = extendDenseElements(tcx, requiredCapacity, extra);
if (edr != ED_OK)
return edr;
}

View File

@ -226,6 +226,8 @@ struct Runtime
namespace js {
struct ThreadSafeContext;
class Allocator;
class SkipRoot;
@ -273,7 +275,7 @@ template <> struct RootKind<JS::Value> : SpecificRootKind<JS::Value, THING_ROOT_
struct ContextFriendFields
{
protected:
JSRuntime *const runtime_;
JSRuntime *const runtime_;
/* The current compartment. */
JSCompartment *compartment_;

View File

@ -68,7 +68,7 @@ ForkJoinSlice::releaseContext()
}
bool
ForkJoinSlice::isMainThread()
ForkJoinSlice::isMainThread() const
{
return true;
}
@ -387,6 +387,7 @@ class ForkJoinShared : public TaskExecutor, public Monitor
void setAbortFlag(bool fatal);
JSRuntime *runtime() { return cx_->runtime(); }
JS::Zone *zone() { return cx_->zone(); }
JSContext *acquireContext() { PR_Lock(cxLock_); return cx_; }
void releaseContext() { PR_Unlock(cxLock_); }
@ -1326,7 +1327,7 @@ ForkJoinShared::init()
return false;
for (unsigned i = 0; i < numSlices_; i++) {
Allocator *allocator = cx_->runtime()->new_<Allocator>(cx_->zone());
Allocator *allocator = cx_->new_<Allocator>(cx_->zone());
if (!allocator)
return false;
@ -1411,6 +1412,7 @@ ForkJoinShared::executeFromWorker(uint32_t workerId, uintptr_t stackLimit)
PerThreadData thisThread(cx_->runtime());
TlsPerThreadData.set(&thisThread);
// Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the
// lock has not been initialized in these cases.
thisThread.ionStackLimit = stackLimit;
@ -1645,16 +1647,22 @@ ForkJoinSlice::ForkJoinSlice(PerThreadData *perThreadData,
uint32_t sliceId, uint32_t numSlices,
Allocator *allocator, ForkJoinShared *shared,
ParallelBailoutRecord *bailoutRecord)
: perThreadData(perThreadData),
sliceId(sliceId),
numSlices(numSlices),
allocator(allocator),
bailoutRecord(bailoutRecord),
shared(shared)
{ }
: ThreadSafeContext(shared->runtime(), perThreadData, Context_ForkJoin),
sliceId(sliceId),
numSlices(numSlices),
allocator(allocator),
bailoutRecord(bailoutRecord),
shared(shared)
{
/*
* Unsafely set the zone. This is used to track malloc counters and to
* trigger GCs and is otherwise not thread-safe to access.
*/
zone_ = shared->zone();
}
bool
ForkJoinSlice::isMainThread()
ForkJoinSlice::isMainThread() const
{
return perThreadData == &shared->runtime()->mainThread;
}

View File

@ -290,12 +290,9 @@ struct ParallelBailoutRecord {
struct ForkJoinShared;
struct ForkJoinSlice
struct ForkJoinSlice : ThreadSafeContext
{
public:
// PerThreadData corresponding to the current worker thread.
PerThreadData *perThreadData;
// Which slice should you process? Ranges from 0 to |numSlices|.
const uint32_t sliceId;
@ -320,7 +317,7 @@ struct ForkJoinSlice
ParallelBailoutRecord *bailoutRecord);
// True if this is the main thread, false if it is one of the parallel workers.
bool isMainThread();
bool isMainThread() const;
// When the code would normally trigger a GC, we don't trigger it
// immediately but instead record that request here. This will