Bug 749617 - Optimize js::CloneScript (r=njn,a=not-libxul)

--HG--
extra : rebase_source : a18f454312960f048b0ef15a3b3b7d48f9ba49f8
This commit is contained in:
Luke Wagner 2012-05-01 20:39:05 -07:00
parent f3f59663e8
commit 3fd46e14d2
10 changed files with 351 additions and 43 deletions

View File

@ -0,0 +1,24 @@
var g = newGlobal('new-compartment');
g.f = new Function('return function(x) { return x }');
assertEq(g.eval("clone(f)()(9)"), 9);
g.f = new Function('return function(x) { let(y = x+1) { return y } }');
assertEq(g.eval("clone(f)()(9)"), 10);
g.f = new Function('return function(x) { let(y = x, z = 1) { return y+z } }');
assertEq(g.eval("clone(f)()(9)"), 10);
g.f = new Function('return function(x) { return x.search(/ponies/) }');
assertEq(g.eval("clone(f)()('123ponies')"), 3);
g.f = new Function('return function(x,y) { return x.search(/a/) + y.search(/b/) }');
assertEq(g.eval("clone(f)()('12a','foo')"), 1);
g.f = new Function('return [function(x) x+2, function(y) let(z=y+1) z]');
assertEq(g.eval("let ([f,g] = clone(f)()) f(g(4))"), 7);
g.f = new Function('return function(x) { switch(x) { case "a": return "b"; case null: return "c" } }');
assertEq(g.eval("clone(f)()('a')"), "b");
assertEq(g.eval("clone(f)()(null)"), "c");
assertEq(g.eval("clone(f)()(3)"), undefined);

View File

@ -374,6 +374,7 @@ template<XDRMode mode>
bool
js::XDRInterpretedFunction(XDRState<mode> *xdr, JSObject **objp, JSScript *parentScript)
{
/* NB: Keep this in sync with CloneInterpretedFunction. */
JSFunction *fun;
JSAtom *atom;
uint32_t firstword; /* flag telling whether fun->atom is non-null,
@ -442,6 +443,35 @@ js::XDRInterpretedFunction(XDRState<XDR_ENCODE> *xdr, JSObject **objp, JSScript
template bool
js::XDRInterpretedFunction(XDRState<XDR_DECODE> *xdr, JSObject **objp, JSScript *parentScript);
JSObject *
js::CloneInterpretedFunction(JSContext *cx, JSFunction *srcFun)
{
/* NB: Keep this in sync with XDRInterpretedFunction. */
RootedVarObject parent(cx, NULL);
JSFunction *clone = js_NewFunction(cx, NULL, NULL, 0, JSFUN_INTERPRETED, parent, NULL);
if (!clone)
return NULL;
if (!clone->clearParent(cx))
return NULL;
if (!clone->clearType(cx))
return NULL;
JSScript *clonedScript = CloneScript(cx, srcFun->script());
if (!clonedScript)
return NULL;
clone->nargs = srcFun->nargs;
clone->flags = srcFun->flags;
clone->atom.init(srcFun->atom);
clone->initScript(clonedScript);
if (!clonedScript->typeSetFunction(cx, clone))
return NULL;
js_CallNewScriptHook(cx, clone->script(), clone);
return clone;
}
/*
* [[HasInstance]] internal method for Function objects: fetch the .prototype
* property of its 'this' parameter, and walks the prototype chain of v (only

View File

@ -311,6 +311,9 @@ template<XDRMode mode>
bool
XDRInterpretedFunction(XDRState<mode> *xdr, JSObject **objp, JSScript *parentScript);
extern JSObject *
CloneInterpretedFunction(JSContext *cx, JSFunction *fun);
} /* namespace js */
extern JSBool

View File

@ -76,6 +76,8 @@
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
#include "vm/RegExpObject-inl.h"
using namespace js;
using namespace js::gc;
using namespace js::frontend;
@ -217,7 +219,8 @@ bool
Bindings::getLocalNameArray(JSContext *cx, BindingNames *namesp)
{
JS_ASSERT(lastBinding);
JS_ASSERT(count() > 0);
if (count() == 0)
return true;
BindingNames &names = *namesp;
JS_ASSERT(names.empty());
@ -395,6 +398,8 @@ template<XDRMode mode>
bool
js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
{
/* NB: Keep this in sync with CloneScript. */
enum ScriptBits {
NoScriptRval,
SavedCallerFun,
@ -1120,11 +1125,10 @@ JS_STATIC_ASSERT(sizeof(JSConstArray) +
< JSScript::INVALID_OFFSET);
JS_STATIC_ASSERT(JSScript::INVALID_OFFSET <= 255);
JSScript *
JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
uint32_t nobjects, uint32_t nregexps,
uint32_t ntrynotes, uint32_t nconsts, uint32_t nglobals,
uint16_t nClosedArgs, uint16_t nClosedVars, uint32_t nTypeSets, JSVersion version)
static inline size_t
ScriptDataSize(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts,
uint32_t nglobals, uint16_t nClosedArgs, uint16_t nClosedVars)
{
size_t size = 0;
@ -1146,16 +1150,33 @@ JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t
size += length * sizeof(jsbytecode);
size += nsrcnotes * sizeof(jssrcnote);
return size;
}
/*
* We assume that calloc aligns on sizeof(Value) if the size we ask to
* allocate divides sizeof(Value).
*/
JS_STATIC_ASSERT(sizeof(Value) == sizeof(double));
static inline uint8_t *
AllocScriptData(JSContext *cx, size_t size)
{
uint8_t *data = static_cast<uint8_t *>(cx->calloc_(JS_ROUNDUP(size, sizeof(Value))));
if (!data)
return NULL;
JS_ASSERT(size_t(data) % sizeof(Value) == 0);
return data;
}
JSScript *
JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
uint32_t nobjects, uint32_t nregexps,
uint32_t ntrynotes, uint32_t nconsts, uint32_t nglobals,
uint16_t nClosedArgs, uint16_t nClosedVars, uint32_t nTypeSets, JSVersion version)
{
size_t size = ScriptDataSize(cx, length, nsrcnotes, natoms, nobjects, nregexps,
ntrynotes, nconsts, nglobals, nClosedArgs, nClosedVars);
uint8_t *data = AllocScriptData(cx, size);
if (!data)
return NULL;
JSScript *script = js_NewGCScript(cx);
if (!script) {
Foreground::free_(data);
@ -1449,6 +1470,10 @@ JSScript::NewScriptFromEmitter(JSContext *cx, BytecodeEmitter *bce)
Debugger::onNewScript(cx, script, compileAndGoGlobal);
}
/*
* initScriptCounts updates scriptCountsMap if necessary. The other script
* maps in JSCompartment are populated lazily.
*/
if (cx->hasRunOption(JSOPTION_PCCOUNT))
(void) script->initScriptCounts(cx);
@ -1738,28 +1763,186 @@ CurrentScriptFileLineOriginSlow(JSContext *cx, const char **file, unsigned *line
} /* namespace js */
JSScript *
js::CloneScript(JSContext *cx, JSScript *script)
template <class T>
static inline T *
Rebase(JSScript *dst, JSScript *src, T *srcp)
{
JS_ASSERT(cx->compartment != script->compartment());
size_t off = reinterpret_cast<uint8_t *>(srcp) - src->data;
return reinterpret_cast<T *>(dst->data + off);
}
/* Serialize script. */
XDREncoder encoder(cx);
JSScript *
js::CloneScript(JSContext *cx, JSScript *src)
{
/* NB: Keep this in sync with XDRScript. */
if (!XDRScript(&encoder, &script, NULL))
uint32_t nconsts = JSScript::isValidOffset(src->constsOffset) ? src->consts()->length : 0;
uint32_t nobjects = JSScript::isValidOffset(src->objectsOffset) ? src->objects()->length : 0;
uint32_t nregexps = JSScript::isValidOffset(src->regexpsOffset) ? src->regexps()->length : 0;
uint32_t ntrynotes = JSScript::isValidOffset(src->trynotesOffset) ? src->trynotes()->length : 0;
uint32_t nClosedArgs = src->numClosedArgs();
uint32_t nClosedVars = src->numClosedVars();
JS_ASSERT(!JSScript::isValidOffset(src->globalsOffset));
uint32_t nglobals = 0;
/* Script data */
size_t size = ScriptDataSize(cx, src->length, src->numNotes(), src->natoms,
nobjects, nregexps, ntrynotes, nconsts, nglobals,
nClosedArgs, nClosedVars);
uint8_t *data = AllocScriptData(cx, size);
if (!data)
return NULL;
uint32_t nbytes;
const void *p = encoder.getData(&nbytes);
/* Bindings */
/* De-serialize script. */
XDRDecoder decoder(cx, p, nbytes, cx->compartment->principals, script->originPrincipals);
Bindings bindings(cx);
BindingNames names(cx);
if (!src->bindings.getLocalNameArray(cx, &names))
return false;
JSScript *newScript;
if (!XDRScript(&decoder, &newScript, NULL))
for (unsigned i = 0; i < names.length(); ++i) {
if (JSAtom *atom = names[i].maybeAtom) {
if (!bindings.add(cx, RootedVarAtom(cx, atom), names[i].kind))
return false;
} else {
uint16_t _;
if (!bindings.addDestructuring(cx, &_))
return false;
}
}
if (!bindings.ensureShape(cx))
return false;
bindings.makeImmutable();
/* Objects */
AutoObjectVector objects(cx);
if (nobjects != 0) {
HeapPtrObject *vector = src->objects()->vector;
for (unsigned i = 0; i < nobjects; i++) {
JSObject *clone = vector[i]->isStaticBlock()
? CloneStaticBlockObject(cx, vector[i]->asStaticBlock(), objects, src)
: CloneInterpretedFunction(cx, vector[i]->toFunction());
if (!clone || !objects.append(clone))
return false;
}
}
/* RegExps */
AutoObjectVector regexps(cx);
for (unsigned i = 0; i < nregexps; i++) {
HeapPtrObject *vector = src->regexps()->vector;
for (unsigned i = 0; i < nregexps; i++) {
JSObject *clone = CloneScriptRegExpObject(cx, vector[i]->asRegExp());
if (!clone || !regexps.append(clone))
return false;
}
}
/* Now that all fallible allocation is complete, create the GC thing. */
JSScript *dst = js_NewGCScript(cx);
if (!dst) {
Foreground::free_(data);
return NULL;
}
return newScript;
PodZero(dst);
new (&dst->bindings) Bindings(cx);
dst->bindings.transfer(cx, &bindings);
/* This assignment must occur before all the Rebase calls. */
dst->data = data;
memcpy(data, src->data, size);
dst->code = Rebase<jsbytecode>(dst, src, src->code);
/* Script filenames are runtime-wide. */
dst->filename = src->filename;
/* Atoms are runtime-wide. */
if (src->natoms != 0)
dst->atoms = Rebase<HeapPtrAtom>(dst, src, src->atoms);
dst->principals = cx->compartment->principals;
if (dst->principals)
JS_HoldPrincipals(dst->principals);
/* Establish invariant: principals implies originPrincipals. */
dst->originPrincipals = src->originPrincipals;
if (!dst->originPrincipals)
dst->originPrincipals = dst->principals;
if (dst->originPrincipals)
JS_HoldPrincipals(dst->originPrincipals);
dst->length = src->length;
dst->lineno = src->lineno;
dst->mainOffset = src->mainOffset;
dst->natoms = src->natoms;
dst->setVersion(src->getVersion());
dst->nfixed = src->nfixed;
dst->nTypeSets = src->nTypeSets;
dst->nslots = src->nslots;
dst->staticLevel = src->staticLevel;
if (src->argumentsHasLocalBinding()) {
dst->setArgumentsHasLocalBinding(src->argumentsLocalSlot());
if (src->analyzedArgsUsage())
dst->setNeedsArgsObj(src->needsArgsObj());
}
dst->constsOffset = src->constsOffset;
dst->objectsOffset = src->objectsOffset;
dst->regexpsOffset = src->regexpsOffset;
dst->trynotesOffset = src->trynotesOffset;
dst->globalsOffset = src->globalsOffset;
dst->closedArgsOffset = src->closedArgsOffset;
dst->closedVarsOffset = src->closedVarsOffset;
dst->noScriptRval = src->noScriptRval;
dst->savedCallerFun = src->savedCallerFun;
dst->strictModeCode = src->strictModeCode;
dst->compileAndGo = src->compileAndGo;
dst->bindingsAccessedDynamically = src->bindingsAccessedDynamically;
dst->hasSingletons = src->hasSingletons;
dst->isGenerator = src->isGenerator;
/*
* initScriptCounts updates scriptCountsMap if necessary. The other script
* maps in JSCompartment are populated lazily.
*/
if (cx->hasRunOption(JSOPTION_PCCOUNT))
(void) dst->initScriptCounts(cx);
if (nconsts != 0) {
HeapValue *vector = Rebase<HeapValue>(dst, src, src->consts()->vector);
dst->consts()->vector = vector;
for (unsigned i = 0; i < nconsts; ++i)
JS_ASSERT_IF(vector[i].isMarkable(), vector[i].toString()->isAtom());
}
if (nobjects != 0) {
HeapPtrObject *vector = Rebase<HeapPtr<JSObject> >(dst, src, src->objects()->vector);
dst->objects()->vector = vector;
for (unsigned i = 0; i < nobjects; ++i)
vector[i].init(objects[i]);
}
if (nregexps != 0) {
HeapPtrObject *vector = Rebase<HeapPtr<JSObject> >(dst, src, src->regexps()->vector);
dst->regexps()->vector = vector;
for (unsigned i = 0; i < nregexps; ++i)
vector[i].init(regexps[i]);
}
if (ntrynotes != 0)
dst->trynotes()->vector = Rebase<JSTryNote>(dst, src, src->trynotes()->vector);
if (nClosedArgs != 0)
dst->closedArgs()->vector = Rebase<uint32_t>(dst, src, src->closedArgs()->vector);
if (nClosedVars != 0)
dst->closedVars()->vector = Rebase<uint32_t>(dst, src, src->closedVars()->vector);
JS_ASSERT(nglobals == 0);
return dst;
}
DebugScript *

View File

@ -592,6 +592,8 @@ struct JSScript : public js::gc::Cell
JSVersion version);
static JSScript *NewScriptFromEmitter(JSContext *cx, js::BytecodeEmitter *bce);
void setVersion(JSVersion v) { version = v; }
/* See TCF_ARGUMENTS_HAS_LOCAL_BINDING comment. */
bool argumentsHasLocalBinding() const { return argsHasLocalBinding_; }
jsbytecode *argumentsBytecode() const { JS_ASSERT(code[0] == JSOP_ARGUMENTS); return code; }

View File

@ -760,6 +760,8 @@ template<XDRMode mode>
bool
js::XDRScriptRegExpObject(XDRState<mode> *xdr, HeapPtrObject *objp)
{
/* NB: Keep this in sync with CloneScriptRegExpObject. */
RootedVarAtom source(xdr->cx());
uint32_t flagsword = 0;
@ -791,3 +793,19 @@ js::XDRScriptRegExpObject(XDRState<XDR_ENCODE> *xdr, HeapPtrObject *objp);
template bool
js::XDRScriptRegExpObject(XDRState<XDR_DECODE> *xdr, HeapPtrObject *objp);
JSObject *
js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj)
{
/* NB: Keep this in sync with XDRScriptRegExpObject. */
RootedVarAtom source(cx, reobj.getSource());
RegExpObject *clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), NULL);
if (!clone)
return NULL;
if (!clone->clearParent(cx))
return false;
if (!clone->clearType(cx))
return false;
return clone;
}

View File

@ -474,6 +474,9 @@ template<XDRMode mode>
bool
XDRScriptRegExpObject(XDRState<mode> *xdr, HeapPtrObject *objp);
extern JSObject *
CloneScriptRegExpObject(JSContext *cx, RegExpObject &re);
} /* namespace js */
#endif

View File

@ -878,18 +878,23 @@ Class js::BlockClass = {
#define NO_PARENT_INDEX UINT32_MAX
/*
* If there's a parent id, then get the parent out of our script's object
* array. We know that we clone block objects in outer-to-inner order, which
* means that getting the parent now will work.
*/
static uint32_t
FindObjectIndex(JSObjectArray *array, JSObject *obj)
FindObjectIndex(JSScript *script, StaticBlockObject *maybeBlock)
{
size_t i;
if (!maybeBlock || !JSScript::isValidOffset(script->objectsOffset))
return NO_PARENT_INDEX;
if (array) {
i = array->length;
do {
if (array->vector[--i] == obj)
return i;
} while (i != 0);
JSObjectArray *objects = script->objects();
HeapPtrObject *vector = objects->vector;
unsigned length = objects->length;
for (unsigned i = 0; i < length; ++i) {
if (vector[i] == maybeBlock)
return i;
}
return NO_PARENT_INDEX;
@ -899,6 +904,8 @@ template<XDRMode mode>
bool
js::XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObject **objp)
{
/* NB: Keep this in sync with CloneStaticBlockObject. */
JSContext *cx = xdr->cx();
StaticBlockObject *obj = NULL;
@ -907,9 +914,7 @@ js::XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObjec
uint32_t depthAndCount = 0;
if (mode == XDR_ENCODE) {
obj = *objp;
parentId = JSScript::isValidOffset(script->objectsOffset)
? FindObjectIndex(script->objects(), obj->enclosingBlock())
: NO_PARENT_INDEX;
parentId = FindObjectIndex(script, obj->enclosingBlock());
uint32_t depth = obj->stackDepth();
JS_ASSERT(depth <= UINT16_MAX);
count = obj->slotCount();
@ -927,11 +932,6 @@ js::XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObjec
return false;
*objp = obj;
/*
* If there's a parent id, then get the parent out of our script's
* object array. We know that we XDR block object in outer-to-inner
* order, which means that getting the parent now will work.
*/
obj->setEnclosingBlock(parentId == NO_PARENT_INDEX
? NULL
: &script->getObject(parentId)->asStaticBlock());
@ -976,7 +976,8 @@ js::XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObjec
}
} else {
AutoShapeVector shapes(cx);
shapes.growBy(count);
if (!shapes.growBy(count))
return false;
for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) {
const Shape *shape = &r.front();
@ -1016,3 +1017,43 @@ js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *xdr, JSScript *script, StaticBloc
template bool
js::XDRStaticBlockObject(XDRState<XDR_DECODE> *xdr, JSScript *script, StaticBlockObject **objp);
JSObject *
js::CloneStaticBlockObject(JSContext *cx, StaticBlockObject &srcBlock,
const AutoObjectVector &objects, JSScript *src)
{
/* NB: Keep this in sync with XDRStaticBlockObject. */
StaticBlockObject *clone = StaticBlockObject::create(cx);
if (!clone)
return false;
uint32_t parentId = FindObjectIndex(src, srcBlock.enclosingBlock());
clone->setEnclosingBlock(parentId == NO_PARENT_INDEX
? NULL
: &objects[parentId]->asStaticBlock());
clone->setStackDepth(srcBlock.stackDepth());
/* Shape::Range is reverse order, so build a list in forward order. */
AutoShapeVector shapes(cx);
if (!shapes.growBy(srcBlock.slotCount()))
return false;
for (Shape::Range r = srcBlock.lastProperty()->all(); !r.empty(); r.popFront())
shapes[r.front().shortid()] = &r.front();
for (const Shape **p = shapes.begin(); p != shapes.end(); ++p) {
jsid id = (*p)->propid();
unsigned i = (*p)->shortid();
bool redeclared;
if (!clone->addVar(cx, id, i, &redeclared)) {
JS_ASSERT(!redeclared);
return false;
}
clone->setAliased(i, srcBlock.isAliased(i));
}
return clone;
}

View File

@ -299,6 +299,10 @@ template<XDRMode mode>
bool
XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObject **objp);
extern JSObject *
CloneStaticBlockObject(JSContext *cx, StaticBlockObject &srcBlock,
const AutoObjectVector &objects, JSScript *src);
} /* namespace js */
#endif /* ScopeObject_h___ */

View File

@ -57,7 +57,7 @@ namespace js {
* and saved versions. If deserialization fails, the data should be
* invalidated if possible.
*/
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 114);
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 115);
class XDRBuffer {
public: