Bug 634654 - Add RegExpPrivate cache. (r=Waldo)

This commit is contained in:
Chris Leary 2011-11-07 13:42:31 -08:00
parent 033bc59a4b
commit 5559fb1d83
9 changed files with 373 additions and 172 deletions

View File

@ -206,14 +206,6 @@ EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped)
return sb.empty() ? unescaped : sb.finishString();
}
static bool
ResetRegExpObjectWithStatics(JSContext *cx, RegExpObject *reobj,
JSLinearString *str, RegExpFlag flags = RegExpFlag(0))
{
flags = RegExpFlag(flags | cx->regExpStatics()->getFlags());
return reobj->reset(cx, str, flags);
}
/*
* Compile a new |RegExpPrivate| for the |RegExpObject|.
*
@ -226,12 +218,15 @@ ResetRegExpObjectWithStatics(JSContext *cx, RegExpObject *reobj,
* flags := ToString(flags) if defined(flags) else ''
*/
static bool
CompileRegExpObject(JSContext *cx, RegExpObject *obj, uintN argc, Value *argv, Value *rval)
CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder,
uintN argc, Value *argv, Value *rval)
{
if (argc == 0) {
if (!ResetRegExpObjectWithStatics(cx, obj, cx->runtime->emptyString))
RegExpStatics *res = cx->regExpStatics();
RegExpObject *reobj = builder.build(cx->runtime->emptyString, res->getFlags());
if (!reobj)
return false;
*rval = ObjectValue(*obj);
*rval = ObjectValue(*reobj);
return true;
}
@ -249,9 +244,10 @@ CompileRegExpObject(JSContext *cx, RegExpObject *obj, uintN argc, Value *argv, V
return false;
}
if (!obj->reset(cx, sourceObj.asRegExp()))
RegExpObject *reobj = builder.build(sourceObj.asRegExp());
if (!reobj)
return false;
*rval = ObjectValue(*obj);
*rval = ObjectValue(*reobj);
return true;
}
@ -285,9 +281,12 @@ CompileRegExpObject(JSContext *cx, RegExpObject *obj, uintN argc, Value *argv, V
if (!RegExpPrivateCode::checkSyntax(cx, NULL, escapedSourceStr))
return false;
if (!ResetRegExpObjectWithStatics(cx, obj, escapedSourceStr, flags))
return false;
*rval = ObjectValue(*obj);
RegExpStatics *res = cx->regExpStatics();
RegExpObject *reobj = builder.build(escapedSourceStr, RegExpFlag(flags | res->getFlags()));
if (!reobj)
return NULL;
*rval = ObjectValue(*reobj);
return true;
}
@ -301,8 +300,8 @@ regexp_compile(JSContext *cx, uintN argc, Value *vp)
if (!obj)
return ok;
RegExpObject *reobj = obj->asRegExp();
return CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval());
RegExpObjectBuilder builder(cx, obj->asRegExp());
return CompileRegExpObject(cx, builder, args.length(), args.array(), &args.rval());
}
static JSBool
@ -322,14 +321,11 @@ regexp_construct(JSContext *cx, uintN argc, Value *vp)
}
}
JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
if (!obj)
RegExpObjectBuilder builder(cx);
if (!CompileRegExpObject(cx, builder, argc, argv, &JS_RVAL(cx, vp)))
return false;
if (!CompileRegExpObject(cx, obj->asRegExp(), argc, argv, &JS_RVAL(cx, vp)))
return false;
*vp = ObjectValue(*obj);
*vp = ObjectValue(*builder.reobj());
return true;
}
@ -462,9 +458,11 @@ js_InitRegExpClass(JSContext *cx, JSObject *obj)
JSObject *proto = global->createBlankPrototype(cx, &RegExpClass);
if (!proto)
return NULL;
proto->setPrivate(NULL);
RegExpObject *reproto = proto->asRegExp();
if (!reproto->reset(cx, cx->runtime->emptyString, RegExpFlag(0)))
RegExpObjectBuilder builder(cx, reproto);
if (!builder.build(cx->runtime->emptyString, RegExpFlag(0)))
return NULL;
if (!DefinePropertiesAndBrand(cx, proto, NULL, regexp_methods))
@ -526,6 +524,7 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
* have to take a defensive refcount here.
*/
AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(rep));
RegExpStatics *res = cx->regExpStatics();
/* Step 2. */

View File

@ -111,6 +111,7 @@ ThreadData::ThreadData()
#endif
waiveGCQuota(false),
tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
repCache(NULL),
dtoaState(NULL),
nativeStackBase(GetNativeStackBase()),
pendingProxyOperation(NULL),
@ -153,6 +154,31 @@ ThreadData::triggerOperationCallback(JSRuntime *rt)
#endif
}
RegExpPrivateCache *
ThreadData::createRegExpPrivateCache(JSRuntime *rt)
{
JS_ASSERT(!repCache);
RegExpPrivateCache *newCache = rt->new_<RegExpPrivateCache>(rt);
if (!newCache || !newCache->init()) {
rt->delete_<RegExpPrivateCache>(newCache);
return NULL;
}
repCache = newCache;
return repCache;
}
void
ThreadData::purgeRegExpPrivateCache(JSRuntime *rt)
{
if (!repCache)
return;
rt->delete_<RegExpPrivateCache>(repCache);
repCache = NULL;
}
} /* namespace js */
JSScript *
@ -315,6 +341,24 @@ js_PurgeThreads(JSContext *cx)
#endif
}
void
js_PurgeThreads_PostGlobalSweep(JSContext *cx)
{
#ifdef JS_THREADSAFE
for (JSThread::Map::Enum e(cx->runtime->threads);
!e.empty();
e.popFront())
{
JSThread *thread = e.front().value;
JS_ASSERT(!JS_CLIST_IS_EMPTY(&thread->contextList));
thread->data.purgeRegExpPrivateCache(cx->runtime);
}
#else
cx->runtime->threadData.purgeRegExpPrivateCache(cx->runtime);
#endif
}
JSContext *
js_NewContext(JSRuntime *rt, size_t stackChunkSize)
{

View File

@ -197,6 +197,25 @@ struct ThreadData {
static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
LifoAlloc tempLifoAlloc;
private:
js::RegExpPrivateCache *repCache;
js::RegExpPrivateCache *createRegExpPrivateCache(JSRuntime *rt);
public:
js::RegExpPrivateCache *getRegExpPrivateCache() { return repCache; }
/* N.B. caller is responsible for reporting OOM. */
js::RegExpPrivateCache *getOrCreateRegExpPrivateCache(JSRuntime *rt) {
if (repCache)
return repCache;
return createRegExpPrivateCache(rt);
}
/* Called at the end of the global GC sweep phase to deallocate repCache memory. */
void purgeRegExpPrivateCache(JSRuntime *rt);
/*
* The GSN cache is per thread since even multi-cx-per-thread embeddings
* do not interleave js_GetSrcNote calls.
@ -1214,6 +1233,8 @@ struct JSContext
js::GCHelperThread *gcBackgroundFree;
#endif
js::ThreadData *threadData() { return JS_THREAD_DATA(this); }
inline void* malloc_(size_t bytes) {
return runtime->malloc_(bytes, this);
}
@ -1890,6 +1911,9 @@ js_FinishThreads(JSRuntime *rt);
extern void
js_PurgeThreads(JSContext *cx);
extern void
js_PurgeThreads_PostGlobalSweep(JSContext *cx);
namespace js {
#ifdef JS_THREADSAFE

View File

@ -2910,6 +2910,9 @@ GCCycle(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind)
MarkAndSweep(cx, gckind);
if (!comp)
js_PurgeThreads_PostGlobalSweep(cx);
#ifdef JS_THREADSAFE
if (gckind != GC_LAST_CONTEXT && rt->state != JSRTS_LANDING) {
JS_ASSERT(cx->gcBackgroundFree == &rt->gcHelperThread);

View File

@ -124,6 +124,7 @@ struct Class;
class RegExpObject;
class RegExpPrivate;
class RegExpObjectBuilder;
class RegExpStatics;
class MatchPairs;
@ -232,6 +233,9 @@ typedef HashMap<jsbytecode *, BreakpointSite *, DefaultHasher<jsbytecode *>, Run
class Debugger;
class WatchpointMap;
typedef HashMap<JSAtom *, RegExpPrivate *, DefaultHasher<JSAtom *>, RuntimeAllocPolicy>
RegExpPrivateCache;
typedef JSNative Native;
typedef JSPropertyOp PropertyOp;
typedef JSStrictPropertyOp StrictPropertyOp;

View File

@ -1264,8 +1264,14 @@ class FlatMatch
int32 match() const { return match_; }
};
/*
* Some string methods operate on a RegExpObject, if it is present, but if it
* is absent create an internal regular expression matcher. This unifies the
* interface.
*/
class RegExpPair
{
JSContext *cx;
AutoRefCount<RegExpPrivate> rep_;
RegExpObject *reobj_;
@ -1273,9 +1279,10 @@ class RegExpPair
void operator=(const RegExpPair &);
public:
explicit RegExpPair(JSContext *cx) : rep_(cx) {}
explicit RegExpPair(JSContext *cx) : cx(cx), rep_(cx) {}
bool resetWithObject(JSContext *cx, RegExpObject *reobj) {
JS_ASSERT(cx == this->cx);
reobj_ = reobj;
RegExpPrivate *rep = reobj_->asRegExp()->getOrCreatePrivate(cx);
if (!rep)
@ -1302,9 +1309,8 @@ class RegExpPair
/*
* RegExpGuard factors logic out of String regexp operations.
*
* @param optarg Indicates in which argument position RegExp
* flags will be found, if present. This is a Mozilla
* extension and not part of any ECMA spec.
* |optarg| indicates in which argument position RegExp flags will be found, if
* present. This is a Mozilla extension and not part of any ECMA spec.
*/
class RegExpGuard
{
@ -1368,8 +1374,9 @@ class RegExpGuard
* Attempt to match |patstr| to |textstr|. A flags argument, metachars in the
* pattern string, or a lengthy pattern string can thwart this process.
*
* @param checkMetaChars Look for regexp metachars in the pattern string.
* @return Whether flat matching could be used.
* |checkMetaChars| looks for regexp metachars in the pattern string.
*
* Return whether flat matching could be used.
*
* N.B. tryFlatMatch returns NULL on OOM, so the caller must check cx->isExceptionPending().
*/

View File

@ -89,6 +89,12 @@ HasRegExpMetaChars(const jschar *chars, size_t length)
return false;
}
inline void
RegExpObject::setPrivate(RegExpPrivate *rep)
{
JSObject::setPrivate(rep);
}
inline RegExpObject *
RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *tokenStream)
@ -101,57 +107,44 @@ inline RegExpObject *
RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *tokenStream)
{
JSLinearString *source = js_NewStringCopyN(cx, chars, length);
JSAtom *source = js_AtomizeChars(cx, chars, length);
if (!source)
return NULL;
/* |NewBuiltinClassInstance| can GC. */
JS::Anchor<JSString *> anchor(source);
return createNoStatics(cx, source, flags, tokenStream);
}
inline RegExpObject *
RegExpObject::createNoStatics(JSContext *cx, JSAtom *source, RegExpFlag flags,
TokenStream *tokenStream)
{
if (!RegExpPrivateCode::checkSyntax(cx, tokenStream, source))
return NULL;
JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
if (!obj)
return NULL;
RegExpObjectBuilder builder(cx);
return builder.build(source, flags);
}
RegExpObject *reobj = obj->asRegExp();
return reobj->reset(cx, source, flags) ? reobj : NULL;
inline void
RegExpObject::purge(JSContext *cx)
{
if (RegExpPrivate *rep = getPrivate()) {
rep->decref(cx);
setPrivate(NULL);
}
}
inline void
RegExpObject::finalize(JSContext *cx)
{
if (RegExpPrivate *rep = getPrivate())
rep->decref(cx);
purge(cx);
#ifdef DEBUG
setPrivate((void *) 0x1); /* Non-null but still in the zero page. */
setPrivate((RegExpPrivate *) 0x1); /* Non-null but still in the zero page. */
#endif
}
inline bool
RegExpObject::reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep)
{
if (!reset(cx, rep->getSource(), rep->getFlags()))
return false;
setPrivate(rep.get());
return true;
}
inline bool
RegExpObject::reset(JSContext *cx, RegExpObject *other)
{
if (RegExpPrivate *rep = other->getPrivate()) {
rep->incref(cx);
return reset(cx, AlreadyIncRefed<RegExpPrivate>(rep));
}
return reset(cx, other->getSource(), other->getFlags());
}
inline bool
RegExpObject::reset(JSContext *cx, JSLinearString *source, RegExpFlag flags)
RegExpObject::init(JSContext *cx, JSLinearString *source, RegExpFlag flags)
{
if (nativeEmpty()) {
const js::Shape **shapep = &cx->compartment->initialRegExpShape;
@ -174,8 +167,8 @@ RegExpObject::reset(JSContext *cx, JSLinearString *source, RegExpFlag flags)
MULTILINE_FLAG_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot == STICKY_FLAG_SLOT);
JS_ASSERT(!getPrivate());
zeroLastIndex();
setPrivate(NULL);
setSource(source);
setGlobal(flags & GlobalFlag);
setIgnoreCase(flags & IgnoreCaseFlag);
@ -191,11 +184,46 @@ RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, T
{
typedef AlreadyIncRefed<RegExpPrivate> RetType;
/*
* We choose to only cache |RegExpPrivate|s who have atoms as
* sources, under the unverified premise that non-atoms will have a
* low hit rate (both hit ratio and absolute number of hits).
*/
bool cacheable = source->isAtom();
/*
* Refcount note: not all |RegExpPrivate|s are cached so we need to
* keep a refcount. The cache holds a "weak ref", where the
* |RegExpPrivate|'s deallocation decref will first cause it to
* remove itself from the cache.
*/
JSRuntime *rt = cx->runtime;
RegExpPrivateCache *cache = NULL; /* Quell "may be used uninitialized". */
RegExpPrivateCache::AddPtr addPtr;
if (cacheable) {
cache = cx->threadData()->getOrCreateRegExpPrivateCache(rt);
if (!cache) {
js_ReportOutOfMemory(cx);
return RetType(NULL);
}
addPtr = cache->lookupForAdd(&source->asAtom());
if (addPtr) {
RegExpPrivate *cached = addPtr->value;
if (cached->getFlags() == flags) {
cached->incref(cx);
return RetType(cached);
}
/* Note: on flag mismatch, we clobber the existing entry. */
}
}
JSLinearString *flatSource = source->ensureLinear(cx);
if (!flatSource)
return RetType(NULL);
RegExpPrivate *self = cx->new_<RegExpPrivate>(flatSource, flags, cx->compartment);
RegExpPrivate *self = cx->new_<RegExpPrivate>(flatSource, flags);
if (!self)
return RetType(NULL);
@ -204,6 +232,18 @@ RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, T
return RetType(NULL);
}
if (cacheable) {
if (addPtr) {
JS_ASSERT(addPtr->key == &self->getSource()->asAtom());
addPtr->value = self;
} else {
if (!cache->add(addPtr, &source->asAtom(), self)) {
js_ReportOutOfMemory(cx);
return RetType(NULL);
}
}
}
return RetType(self);
}
@ -331,40 +371,23 @@ RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t length, si
inline void
RegExpPrivate::incref(JSContext *cx)
{
#ifdef DEBUG
assertSameCompartment(cx, compartment);
#endif
++refCount;
}
inline void
RegExpPrivate::decref(JSContext *cx)
{
#ifdef DEBUG
assertSameCompartment(cx, compartment);
#endif
if (--refCount == 0)
cx->delete_(this);
}
if (--refCount != 0)
return;
inline RegExpPrivate *
RegExpObject::getOrCreatePrivate(JSContext *cx)
{
if (RegExpPrivate *rep = getPrivate())
return rep;
RegExpPrivateCache *cache;
if (source->isAtom() && (cache = cx->threadData()->getRegExpPrivateCache())) {
RegExpPrivateCache::Ptr ptr = cache->lookup(&source->asAtom());
if (ptr && ptr->value == this)
cache->remove(ptr);
}
return makePrivate(cx) ? getPrivate() : NULL;
}
inline RegExpPrivate *
RegExpObject::getPrivate() const
{
RegExpPrivate *rep = static_cast<RegExpPrivate *>(JSObject::getPrivate());
#ifdef DEBUG
if (rep)
CompartmentChecker::check(compartment(), rep->compartment);
#endif
return rep;
cx->delete_(this);
}
} /* namespace js */

View File

@ -59,6 +59,106 @@ JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
/* RegExpObjectBuilder */
bool
RegExpObjectBuilder::getOrCreate()
{
if (reobj_)
return true;
JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
if (!obj)
return false;
obj->setPrivate(NULL);
reobj_ = obj->asRegExp();
return true;
}
bool
RegExpObjectBuilder::getOrCreateClone(RegExpObject *proto)
{
JS_ASSERT(!reobj_);
JSObject *clone = NewNativeClassInstance(cx, &RegExpClass, proto, proto->getParent());
if (!clone)
return false;
clone->setPrivate(NULL);
reobj_ = clone->asRegExp();
return true;
}
RegExpObject *
RegExpObjectBuilder::build(AlreadyIncRefed<RegExpPrivate> rep)
{
if (!getOrCreate()) {
rep->decref(cx);
return NULL;
}
reobj_->purge(cx);
if (!reobj_->init(cx, rep->getSource(), rep->getFlags())) {
rep->decref(cx);
return NULL;
}
reobj_->setPrivate(rep.get());
return reobj_;
}
RegExpObject *
RegExpObjectBuilder::build(JSLinearString *source, RegExpFlag flags)
{
if (!getOrCreate())
return NULL;
reobj_->purge(cx);
return reobj_->init(cx, source, flags) ? reobj_ : NULL;
}
RegExpObject *
RegExpObjectBuilder::build(RegExpObject *other)
{
RegExpPrivate *rep = other->getOrCreatePrivate(cx);
if (!rep)
return NULL;
/* Now, incref it for the RegExpObject being built. */
rep->incref(cx);
return build(AlreadyIncRefed<RegExpPrivate>(rep));
}
RegExpObject *
RegExpObjectBuilder::clone(RegExpObject *other, RegExpObject *proto)
{
if (!getOrCreateClone(proto))
return NULL;
/*
* Check that the RegExpPrivate for the original is okay to use in
* the clone -- if the |RegExpStatics| provides more flags we'll
* need a different |RegExpPrivate|.
*/
RegExpStatics *res = cx->regExpStatics();
RegExpFlag origFlags = other->getFlags();
RegExpFlag staticsFlags = res->getFlags();
if ((origFlags & staticsFlags) != staticsFlags) {
RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
return build(other->getSource(), newFlags);
}
RegExpPrivate *toShare = other->getOrCreatePrivate(cx);
if (!toShare)
return NULL;
toShare->incref(cx);
return build(AlreadyIncRefed<RegExpPrivate>(toShare));
}
/* MatchPairs */
MatchPairs *
MatchPairs::create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount)
{
@ -130,16 +230,16 @@ RegExpPrivate::execute(JSContext *cx, const jschar *chars, size_t length, size_t
return RegExpRunStatus_Success;
}
bool
RegExpPrivate *
RegExpObject::makePrivate(JSContext *cx)
{
JS_ASSERT(!getPrivate());
AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, getSource(), getFlags(), NULL);
if (!rep)
return false;
return NULL;
setPrivate(rep.get());
return true;
return rep.get();
}
RegExpRunStatus
@ -209,13 +309,11 @@ js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp)
if (!JS_XDRString(xdr, &source) || !JS_XDRUint32(xdr, &flagsword))
return false;
if (xdr->mode == JSXDR_DECODE) {
JS::Anchor<JSString *> anchor(source);
const jschar *chars = source->getChars(xdr->cx);
if (!chars)
JSAtom *atom = js_AtomizeString(xdr->cx, source);
if (!atom)
return false;
size_t len = source->length();
RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, chars, len,
RegExpFlag(flagsword), NULL);
RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, atom, RegExpFlag(flagsword),
NULL);
if (!reobj)
return false;
@ -238,6 +336,12 @@ regexp_finalize(JSContext *cx, JSObject *obj)
obj->asRegExp()->finalize(cx);
}
static void
regexp_trace(JSTracer *trc, JSObject *obj)
{
obj->asRegExp()->purge(trc->context);
}
Class js::RegExpClass = {
js_RegExp_str,
JSCLASS_HAS_PRIVATE |
@ -257,7 +361,7 @@ Class js::RegExpClass = {
NULL, /* construct */
js_XDRRegExpObject,
NULL, /* hasInstance */
NULL /* trace */
regexp_trace
};
#if ENABLE_YARR_JIT
@ -378,54 +482,14 @@ RegExpPrivate::create(JSContext *cx, JSLinearString *str, JSString *opt, TokenSt
return create(cx, str, flags, ts);
}
RegExpObject *
RegExpObject::clone(JSContext *cx, RegExpObject *reobj, RegExpObject *proto)
{
JSObject *clone = NewNativeClassInstance(cx, &RegExpClass, proto, proto->getParent());
if (!clone)
return NULL;
/*
* This clone functionality does not duplicate the JIT'd code blob,
* which is necessary for cross-compartment cloning functionality.
*/
assertSameCompartment(cx, reobj, clone);
RegExpStatics *res = cx->regExpStatics();
RegExpObject *reclone = clone->asRegExp();
/*
* Check that the RegExpPrivate for the original is okay to use in
* the clone -- if the |RegExpStatics| provides more flags we'll
* need a different |RegExpPrivate|.
*/
RegExpFlag origFlags = reobj->getFlags();
RegExpFlag staticsFlags = res->getFlags();
if ((origFlags & staticsFlags) != staticsFlags) {
RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
return reclone->reset(cx, reobj->getSource(), newFlags) ? reclone : NULL;
}
RegExpPrivate *toShare = reobj->getOrCreatePrivate(cx);
if (!toShare)
return NULL;
toShare->incref(cx);
if (!reclone->reset(cx, AlreadyIncRefed<RegExpPrivate>(toShare))) {
toShare->decref(cx);
return NULL;
}
return reclone;
}
JSObject * JS_FASTCALL
js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto)
{
JS_ASSERT(obj->isRegExp());
JS_ASSERT(proto->isRegExp());
return RegExpObject::clone(cx, obj->asRegExp(), proto->asRegExp());
RegExpObjectBuilder builder(cx);
return builder.clone(obj->asRegExp(), proto->asRegExp());
}
#ifdef JS_TRACER

View File

@ -82,13 +82,14 @@ class RegExpObject : public ::JSObject
*/
static inline RegExpObject *
create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length, RegExpFlag flags,
TokenStream *ts);
TokenStream *tokenStream);
static inline RegExpObject *
createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
TokenStream *ts);
TokenStream *tokenStream);
static RegExpObject *clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto);
static inline RegExpObject *
createNoStatics(JSContext *cx, JSAtom *atom, RegExpFlag flags, TokenStream *tokenStream);
/* Note: fallible. */
JSFlatString *toString(JSContext *cx) const;
@ -148,27 +149,35 @@ class RegExpObject : public ::JSObject
bool multiline() const { return getSlot(MULTILINE_FLAG_SLOT).toBoolean(); }
bool sticky() const { return getSlot(STICKY_FLAG_SLOT).toBoolean(); }
/*
* N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|, hence
* |reset| for re-initialization.
*/
inline bool reset(JSContext *cx, RegExpObject *other);
inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep);
inline bool reset(JSContext *cx, JSLinearString *source, RegExpFlag flags);
inline RegExpPrivate *getOrCreatePrivate(JSContext *cx);
inline void finalize(JSContext *cx);
/* Clear out lazy |RegExpPrivate|. */
inline void purge(JSContext *x);
RegExpPrivate *getOrCreatePrivate(JSContext *cx) {
if (RegExpPrivate *rep = getPrivate())
return rep;
return makePrivate(cx);
}
private:
friend class RegExpObjectBuilder;
inline bool init(JSContext *cx, JSLinearString *source, RegExpFlag flags);
/* The |RegExpPrivate| is lazily created at the time of use. */
inline RegExpPrivate *getPrivate() const;
RegExpPrivate *getPrivate() const {
return static_cast<RegExpPrivate *>(JSObject::getPrivate());
}
inline void setPrivate(RegExpPrivate *rep);
/*
* Precondition: the syntax for |source| has already been validated.
* Side effect: sets the private field.
*/
bool makePrivate(JSContext *cx);
RegExpPrivate *makePrivate(JSContext *cx);
friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSLinearString *, RegExpFlag);
friend bool ResetRegExpObject(JSContext *, RegExpObject *, AlreadyIncRefed<RegExpPrivate>);
@ -184,6 +193,32 @@ class RegExpObject : public ::JSObject
RegExpObject &operator=(const RegExpObject &reo);
}; /* class RegExpObject */
/* Either builds a new RegExpObject or re-initializes an existing one. */
class RegExpObjectBuilder
{
JSContext *cx;
RegExpObject *reobj_;
bool getOrCreate();
bool getOrCreateClone(RegExpObject *proto);
public:
RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj = NULL)
: cx(cx), reobj_(reobj)
{ }
RegExpObject *reobj() { return reobj_; }
/* Note: In case of failure, |rep| will be decrefed. */
RegExpObject *build(AlreadyIncRefed<RegExpPrivate> rep);
RegExpObject *build(JSLinearString *str, RegExpFlag flags);
RegExpObject *build(RegExpObject *other);
/* Perform a VM-internal clone. */
RegExpObject *clone(RegExpObject *other, RegExpObject *proto);
};
/* Abstracts away the gross |RegExpPrivate| backend details. */
class RegExpPrivateCode
{
@ -243,12 +278,6 @@ class RegExpPrivateCode
static void reportPCREError(JSContext *cx, int error);
#endif
inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
RegExpFlag flags);
inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount);
static size_t getOutputSize(size_t pairCount) {
#if ENABLE_YARR_JIT
return pairCount * 2;
@ -256,6 +285,13 @@ class RegExpPrivateCode
return pairCount * 3; /* Should be x2, but PCRE has... needs. */
#endif
}
inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
RegExpFlag flags);
inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount);
};
/*
@ -265,8 +301,8 @@ class RegExpPrivateCode
* compilation.
*
* Non-atomic refcounting is used, so single-thread invariants must be
* maintained: we check regexp operations are performed in a single
* compartment.
* maintained. |RegExpPrivate|s are currently shared within a single
* |ThreadData|.
*
* Note: refCount cannot overflow because that would require more
* referring regexp objects than there is space for in addressable
@ -280,12 +316,9 @@ class RegExpPrivate
uintN parenCount;
RegExpFlag flags;
public:
DebugOnly<JSCompartment *> compartment;
private:
RegExpPrivate(JSLinearString *source, RegExpFlag flags, JSCompartment *compartment)
: source(source), refCount(1), parenCount(0), flags(flags), compartment(compartment)
RegExpPrivate(JSLinearString *source, RegExpFlag flags)
: source(source), refCount(1), parenCount(0), flags(flags)
{ }
JS_DECLARE_ALLOCATION_FRIENDS_FOR_PRIVATE_CONSTRUCTOR;