Bug 1010441 - Keep RegExpShared and RegExp jitcode around when preserving jitcode in a compartment, r=billm.

This commit is contained in:
Brian Hackett 2014-05-21 11:31:02 -07:00
parent 566a392ad9
commit fe7b4baa60
4 changed files with 134 additions and 145 deletions

View File

@ -569,7 +569,7 @@ class AutoScriptVector : public AutoVectorRooter<JSScript *>
};
/*
* Cutsom rooting behavior for internal and external clients.
* Custom rooting behavior for internal and external clients.
*/
class JS_PUBLIC_API(CustomAutoRooter) : private AutoGCRooter
{

View File

@ -645,8 +645,6 @@ JSCompartment::clearTables()
{
global_ = nullptr;
regExps.clearTables();
// No scripts should have run in this compartment. This is used when
// merging a compartment that has been used off thread into another
// compartment and zone.
@ -658,6 +656,7 @@ JSCompartment::clearTables()
JS_ASSERT(!debugScopes);
JS_ASSERT(!gcWeakMapList);
JS_ASSERT(enumerators->next() == enumerators);
JS_ASSERT(regExps.empty());
types.clearTables();
if (baseShapes.initialized())

View File

@ -89,7 +89,7 @@ RegExpObjectBuilder::build(HandleAtom source, RegExpShared &shared)
if (!reobj_->init(cx, source, shared.getFlags()))
return nullptr;
reobj_->setShared(cx, shared);
reobj_->setShared(shared);
return reobj_;
}
@ -102,6 +102,14 @@ RegExpObjectBuilder::build(HandleAtom source, RegExpFlag flags)
return reobj_->init(cx, source, flags) ? reobj_.get() : nullptr;
}
static inline void
MaybeTraceRegExpShared(JSContext *cx, RegExpShared *shared)
{
Zone *zone = cx->zone();
if (zone->needsBarrier())
shared->trace(zone->barrierTracer());
}
RegExpObject *
RegExpObjectBuilder::clone(Handle<RegExpObject *> other)
{
@ -127,9 +135,12 @@ RegExpObjectBuilder::clone(Handle<RegExpObject *> other)
}
RegExpGuard g(cx);
if (!other->getShared(cx, &g))
if (!other->getShared(cx->asJSContext(), &g))
return nullptr;
g.re()->prepareForUse(cx);
// Copying a RegExpShared from one object to another requires a read
// barrier, as the shared pointer in an object may be weak.
MaybeTraceRegExpShared(cx->asJSContext(), g.re());
Rooted<JSAtom *> source(cx, other->getSource());
return build(source, *g);
@ -217,16 +228,29 @@ VectorMatchPairs::allocOrExpandArray(size_t pairCount)
/* RegExpObject */
static void
regexp_trace(JSTracer *trc, JSObject *obj)
/* static */ void
RegExpObject::trace(JSTracer *trc, JSObject *obj)
{
/*
* We have to check both conditions, since:
* 1. During TraceRuntime, isHeapBusy() is true
* 2. When a write barrier executes, IS_GC_MARKING_TRACER is true.
*/
if (trc->runtime()->isHeapBusy() && IS_GC_MARKING_TRACER(trc))
RegExpShared *shared = obj->as<RegExpObject>().maybeShared();
if (!shared)
return;
// When tracing through the object normally, we have the option of
// unlinking the object from its RegExpShared so that the RegExpShared may
// be collected. To detect this we need to test all the following
// conditions, since:
// 1. During TraceRuntime, isHeapBusy() is true, but the tracer might not
// be a marking tracer.
// 2. When a write barrier executes, IS_GC_MARKING_TRACER is true, but
// isHeapBusy() will be false.
if (trc->runtime()->isHeapBusy() &&
IS_GC_MARKING_TRACER(trc) &&
!obj->tenuredZone()->isPreservingCode())
{
obj->setPrivate(nullptr);
} else {
shared->trace(trc);
}
}
const Class RegExpObject::class_ = {
@ -245,7 +269,7 @@ const Class RegExpObject::class_ = {
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
regexp_trace
RegExpObject::trace
};
RegExpObject *
@ -294,7 +318,7 @@ RegExpObject::createNoStatics(ExclusiveContext *cx, HandleAtom source, RegExpFla
}
bool
RegExpObject::createShared(ExclusiveContext *cx, RegExpGuard *g)
RegExpObject::createShared(JSContext *cx, RegExpGuard *g)
{
Rooted<RegExpObject*> self(cx, this);
@ -302,7 +326,7 @@ RegExpObject::createShared(ExclusiveContext *cx, RegExpGuard *g)
if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g))
return false;
self->setShared(cx, **g);
self->setShared(**g);
return true;
}
@ -400,9 +424,8 @@ RegExpObject::toString(JSContext *cx) const
/* RegExpShared */
RegExpShared::RegExpShared(JSAtom *source, RegExpFlag flags, uint64_t gcNumber)
: source(source), flags(flags), parenCount(0), canStringMatch(false),
activeUseCount(0), gcNumberWhenUsed(gcNumber)
RegExpShared::RegExpShared(JSAtom *source, RegExpFlag flags)
: source(source), flags(flags), parenCount(0), canStringMatch(false), marked_(false)
{
#ifdef JS_YARR
bytecode = nullptr;
@ -426,6 +449,21 @@ RegExpShared::~RegExpShared()
js_delete(tables[i]);
}
void
RegExpShared::trace(JSTracer *trc)
{
if (IS_GC_MARKING_TRACER(trc))
marked_ = true;
if (source)
MarkString(trc, &source, "RegExpShared source");
#if !defined(JS_YARR) && defined(JS_ION)
if (jitCode)
MarkJitCode(trc, &jitCode, "RegExpShared code");
#endif
}
#ifdef JS_YARR
void
@ -854,13 +892,19 @@ RegExpShared::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
/* RegExpCompartment */
RegExpCompartment::RegExpCompartment(JSRuntime *rt)
: map_(rt), inUse_(rt), matchResultTemplateObject_(nullptr)
: set_(rt), matchResultTemplateObject_(nullptr)
{}
RegExpCompartment::~RegExpCompartment()
{
JS_ASSERT_IF(map_.initialized(), map_.empty());
JS_ASSERT_IF(inUse_.initialized(), inUse_.empty());
// Because of stray mark bits being set (see RegExpCompartment::sweep)
// there might still be RegExpShared instances which haven't been deleted.
if (set_.initialized()) {
for (Set::Enum e(set_); !e.empty(); e.popFront()) {
RegExpShared *shared = e.front();
js_delete(shared);
}
}
}
JSObject *
@ -900,7 +944,7 @@ RegExpCompartment::createMatchResultTemplateObject(JSContext *cx)
bool
RegExpCompartment::init(JSContext *cx)
{
if (!map_.init(0) || !inUse_.init(0)) {
if (!set_.init(0)) {
if (cx)
js_ReportOutOfMemory(cx);
return false;
@ -909,20 +953,29 @@ RegExpCompartment::init(JSContext *cx)
return true;
}
/* See the comment on RegExpShared lifetime in RegExpObject.h. */
void
RegExpCompartment::sweep(JSRuntime *rt)
{
#ifdef DEBUG
for (Map::Range r = map_.all(); !r.empty(); r.popFront())
JS_ASSERT(inUse_.has(r.front().value()));
#endif
map_.clear();
for (PendingSet::Enum e(inUse_); !e.empty(); e.popFront()) {
for (Set::Enum e(set_); !e.empty(); e.popFront()) {
RegExpShared *shared = e.front();
if (shared->activeUseCount == 0 && shared->gcNumberWhenUsed < rt->gc.startNumber) {
// Sometimes RegExpShared instances are marked without the
// compartment being subsequently cleared. This can happen if a GC is
// restarted while in progress (i.e. performing a full GC in the
// middle of an incremental GC) or if a RegExpShared referenced via the
// stack is traced but is not in a zone being collected.
//
// Because of this we only treat the marked_ bit as a hint, and destroy
// the RegExpShared if it was accidentally marked earlier but wasn't
// marked by the current trace.
bool keep = shared->marked() && !IsStringAboutToBeFinalized(shared->source.unsafeGet());
#if !defined(JS_YARR) && defined(JS_ION)
if (keep && shared->jitCode)
keep = !IsJitCodeAboutToBeFinalized(shared->jitCode.unsafeGet());
#endif
if (keep) {
shared->clearMarked();
} else {
js_delete(shared);
e.removeFront();
}
@ -935,42 +988,32 @@ RegExpCompartment::sweep(JSRuntime *rt)
}
}
void
RegExpCompartment::clearTables()
{
JS_ASSERT(inUse_.empty());
map_.clear();
}
bool
RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g)
RegExpCompartment::get(JSContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g)
{
Key key(source, flags);
Map::AddPtr p = map_.lookupForAdd(key);
Set::AddPtr p = set_.lookupForAdd(key);
if (p) {
g->init(*p->value());
// Trigger a read barrier on existing RegExpShared instances fetched
// from the table (which only holds weak references).
MaybeTraceRegExpShared(cx, *p);
g->init(**p);
return true;
}
uint64_t gcNumber = cx->zone()->gcNumber();
ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags, gcNumber));
ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags));
if (!shared)
return false;
/* Add to RegExpShared sharing hashmap. */
if (!map_.add(p, key, shared)) {
if (!set_.add(p, shared)) {
js_ReportOutOfMemory(cx);
return false;
}
/* Add to list of all RegExpShared objects in this RegExpCompartment. */
if (!inUse_.put(shared)) {
map_.remove(key);
js_ReportOutOfMemory(cx);
return false;
}
// Trace RegExpShared instances created during an incremental GC.
MaybeTraceRegExpShared(cx, shared);
/* Since error deletes |shared|, only guard |shared| on success. */
g->init(*shared.forget());
return true;
}
@ -989,9 +1032,8 @@ size_t
RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
{
size_t n = 0;
n += map_.sizeOfExcludingThis(mallocSizeOf);
n += inUse_.sizeOfExcludingThis(mallocSizeOf);
for (PendingSet::Enum e(inUse_); !e.empty(); e.popFront()) {
n += set_.sizeOfExcludingThis(mallocSizeOf);
for (Set::Enum e(set_); !e.empty(); e.popFront()) {
RegExpShared *shared = e.front();
n += shared->sizeOfIncludingThis(mallocSizeOf);
}

View File

@ -95,37 +95,19 @@ CloneRegExpObject(JSContext *cx, JSObject *obj);
* A RegExpShared is the compiled representation of a regexp. A RegExpShared is
* potentially pointed to by multiple RegExpObjects. Additionally, C++ code may
* have pointers to RegExpShareds on the stack. The RegExpShareds are kept in a
* cache so that they can be reused when compiling the same regex string.
* table so that they can be reused when compiling the same regex string.
*
* During a GC, the trace hook for RegExpObject clears any pointers to
* RegExpShareds so that there will be no dangling pointers when they are
* deleted. However, some RegExpShareds are not deleted:
*
* 1. Any RegExpShared with pointers from the C++ stack is not deleted.
* 2. Any RegExpShared which has been embedded into jitcode is not deleted.
* This rarely comes into play, as jitcode is usually purged before the
* RegExpShared are sweeped.
* 3. Any RegExpShared that was installed in a RegExpObject during an
* incremental GC is not deleted. This is because the RegExpObject may have
* been traced through before the new RegExpShared was installed, in which
* case deleting the RegExpShared would turn the RegExpObject's reference
* into a dangling pointer
*
* The activeUseCount and gcNumberWhenUsed fields are used to track these
* conditions.
*
* There are two tables used to track RegExpShareds. map_ implements the cache
* and is cleared on every GC. inUse_ logically owns all RegExpShareds in the
* compartment and attempts to delete all RegExpShareds that aren't kept alive
* by the above conditions on every GC sweep phase. It is necessary to use two
* separate tables since map_ *must* be fully cleared on each GC since the Key
* points to a JSAtom that can become garbage.
* During a GC, RegExpShared instances are marked and swept like GC things.
* Usually, RegExpObjects clear their pointers to their RegExpShareds rather
* than explicitly tracing them, so that the RegExpShared and any jitcode can
* be reclaimed quicker. However, the RegExpShareds are traced through by
* objects when we are preserving jitcode in their zone, to avoid the same
* recompilation inefficiencies as normal Ion and baseline compilation.
*/
class RegExpShared
{
friend class RegExpCompartment;
friend class RegExpStatics;
friend class RegExpGuard;
typedef frontend::TokenStream TokenStream;
@ -140,16 +122,13 @@ class RegExpShared
#endif
#endif
/*
* Source to the RegExp, for lazy compilation.
* The source must be rooted while activeUseCount is non-zero
* via RegExpGuard or explicit calls to trace().
*/
JSAtom * source;
/* Source to the RegExp, for lazy compilation. */
HeapPtrAtom source;
RegExpFlag flags;
size_t parenCount;
bool canStringMatch;
bool marked_;
#ifdef JS_YARR
@ -171,10 +150,6 @@ class RegExpShared
// Tables referenced by JIT code.
Vector<uint8_t *, 0, SystemAllocPolicy> tables;
/* Lifetime-preserving variables: see class-level comment above. */
size_t activeUseCount;
uint64_t gcNumberWhenUsed;
/* Internal functions. */
bool compile(JSContext *cx, bool matchOnly, const jschar *sampleChars, size_t sampleLength);
bool compile(JSContext *cx, HandleAtom pattern, bool matchOnly, const jschar *sampleChars, size_t sampleLength);
@ -186,7 +161,7 @@ class RegExpShared
#endif
public:
RegExpShared(JSAtom *source, RegExpFlag flags, uint64_t gcNumber);
RegExpShared(JSAtom *source, RegExpFlag flags);
~RegExpShared();
#ifdef JS_YARR
@ -208,18 +183,6 @@ class RegExpShared
static bool checkSyntax(ExclusiveContext *cx, TokenStream *tokenStream, JSLinearString *source);
#endif // JS_YARR
/* Called when a RegExpShared is installed into a RegExpObject. */
void prepareForUse(ExclusiveContext *cx) {
gcNumberWhenUsed = cx->zone()->gcNumber();
JSString::writeBarrierPre(source);
#ifndef JS_YARR
#ifdef JS_ION
if (jitCode)
jit::JitCode::writeBarrierPre(jitCode);
#endif
#endif // !JS_YARR
}
/* Primary interface: run this regular expression on the given string. */
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length,
size_t *lastIndex, MatchPairs &matches);
@ -242,12 +205,10 @@ class RegExpShared
return parenCount;
}
void incRef() { activeUseCount++; }
void decRef() { JS_ASSERT(activeUseCount > 0); activeUseCount--; }
/* Accounts for the "0" (whole match) pair. */
size_t pairCount() const { return getParenCount() + 1; }
JSAtom *getSource() const { return source; }
RegExpFlag getFlags() const { return flags; }
bool ignoreCase() const { return flags & IgnoreCaseFlag; }
bool global() const { return flags & GlobalFlag; }
@ -289,6 +250,11 @@ class RegExpShared
#endif // JS_YARR
void trace(JSTracer *trc);
bool marked() const { return marked_; }
void clearMarked() { JS_ASSERT(marked_); marked_ = false; }
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
};
@ -322,31 +288,15 @@ class RegExpGuard : public JS::CustomAutoRooter
void init(RegExpShared &re) {
JS_ASSERT(!initialized());
re_ = &re;
re_->incRef();
}
void release() {
if (re_) {
re_->decRef();
re_ = nullptr;
}
re_ = nullptr;
}
virtual void trace(JSTracer *trc) {
if (!re_)
return;
if (re_->source) {
MarkStringRoot(trc, reinterpret_cast<JSString**>(&re_->source),
"RegExpGuard source");
}
#ifndef JS_YARR
#ifdef JS_ION
if (re_->jitCode) {
MarkJitCodeRoot(trc, reinterpret_cast<jit::JitCode**>(&re_->jitCode),
"RegExpGuard code");
}
#endif
#endif // !JS_YARR
if (re_)
re_->trace(trc);
}
bool initialized() const { return !!re_; }
@ -365,6 +315,9 @@ class RegExpCompartment
Key(JSAtom *atom, RegExpFlag flag)
: atom(atom), flag(flag)
{ }
Key(RegExpShared *shared)
: atom(shared->getSource()), flag(shared->getFlags())
{ }
typedef Key Lookup;
static HashNumber hash(const Lookup &l) {
@ -375,20 +328,12 @@ class RegExpCompartment
}
};
/*
* Cache to reuse RegExpShareds with the same source/flags/etc. The cache
* is entirely cleared on each GC.
*/
typedef HashMap<Key, RegExpShared *, Key, RuntimeAllocPolicy> Map;
Map map_;
/*
* The set of all RegExpShareds in the compartment. On every GC, every
* RegExpShared that is not actively being used is deleted and removed from
* the set.
* RegExpShared that was not marked is deleted and removed from the set.
*/
typedef HashSet<RegExpShared *, DefaultHasher<RegExpShared*>, RuntimeAllocPolicy> PendingSet;
PendingSet inUse_;
typedef HashSet<RegExpShared *, Key, RuntimeAllocPolicy> Set;
Set set_;
/*
* This is the template object where the result of re.exec() is based on,
@ -405,9 +350,10 @@ class RegExpCompartment
bool init(JSContext *cx);
void sweep(JSRuntime *rt);
void clearTables();
bool get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g);
bool empty() { return set_.empty(); }
bool get(JSContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g);
/* Like 'get', but compile 'maybeOpt' (if non-null). */
bool get(JSContext *cx, HandleAtom source, JSString *maybeOpt, RegExpGuard *g);
@ -512,7 +458,7 @@ class RegExpObject : public JSObject
g->init(*maybeShared());
}
bool getShared(ExclusiveContext *cx, RegExpGuard *g) {
bool getShared(JSContext *cx, RegExpGuard *g) {
if (RegExpShared *shared = maybeShared()) {
g->init(*shared);
return true;
@ -520,11 +466,13 @@ class RegExpObject : public JSObject
return createShared(cx, g);
}
void setShared(ExclusiveContext *cx, RegExpShared &shared) {
shared.prepareForUse(cx);
void setShared(RegExpShared &shared) {
JS_ASSERT(!maybeShared());
JSObject::setPrivate(&shared);
}
static void trace(JSTracer *trc, JSObject *obj);
private:
friend class RegExpObjectBuilder;
@ -547,7 +495,7 @@ class RegExpObject : public JSObject
* Precondition: the syntax for |source| has already been validated.
* Side effect: sets the private field.
*/
bool createShared(ExclusiveContext *cx, RegExpGuard *g);
bool createShared(JSContext *cx, RegExpGuard *g);
RegExpShared *maybeShared() const {
return static_cast<RegExpShared *>(JSObject::getPrivate());
}