Bug 821361 - Optimize type information in closures that only run once, r=luke.

This commit is contained in:
Brian Hackett 2013-01-20 02:49:21 -07:00
parent c66997672e
commit ecff87a86d
7 changed files with 101 additions and 40 deletions

View File

@ -119,6 +119,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent, Parser *parser, Shared
typesetCount(0),
hasSingletons(false),
emittingForInit(false),
emittingRunOnceLambda(false),
hasGlobalScope(hasGlobalScope),
selfHostingMode(selfHostingMode)
{
@ -1655,14 +1656,20 @@ CheckSideEffects(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool *answe
}
bool
BytecodeEmitter::checkSingletonContext()
BytecodeEmitter::isInLoop()
{
if (!script->compileAndGo || sc->isFunctionBox())
return false;
for (StmtInfoBCE *stmt = topStmt; stmt; stmt = stmt->down) {
if (stmt->isLoop())
return false;
return true;
}
return false;
}
bool
BytecodeEmitter::checkSingletonContext()
{
if (!script->compileAndGo || sc->isFunctionBox() || isInLoop())
return false;
hasSingletons = true;
return true;
}
@ -2593,12 +2600,27 @@ frontend::EmitFunctionScript(JSContext *cx, BytecodeEmitter *bce, ParseNode *bod
if (!JSScript::fullyInitFromEmitter(cx, bce->script, bce))
return false;
/*
* If this function is only expected to run once, mark the script so that
* initializers created within it may be given more precise types.
*/
if (bce->parent && bce->parent->emittingRunOnceLambda)
bce->script->treatAsRunOnce = true;
/* Mark functions which will only be executed once as singletons. */
/*
* Mark as singletons any function which will only be executed once, or
* which is inner to a lambda we only expect to run once. In the latter
* case, if the lambda runs multiple times then CloneFunctionObject will
* make a deep clone of its contents.
*/
bool singleton =
cx->typeInferenceEnabled() &&
bce->script->compileAndGo &&
bce->parent &&
bce->parent->checkSingletonContext();
(bce->parent->checkSingletonContext() ||
(!bce->parent->isInLoop() &&
bce->parent->parent &&
bce->parent->parent->emittingRunOnceLambda));
/* Initialize fun->script() so that the debugger has a valid fun->script(). */
RootedFunction fun(cx, bce->script->function());
@ -5360,6 +5382,24 @@ EmitCallOrNew(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
callop = true; /* suppress JSOP_UNDEFINED after */
break;
#endif
case PNK_FUNCTION:
/*
* Top level lambdas which are immediately invoked should be
* treated as only running once. Every time they execute we will
* create new types and scripts for their contents, to increase
* the quality of type information within them and enable more
* backend optimizations. Note that this does not depend on the
* lambda being invoked at most once (it may be named or be
* accessed via foo.caller indirection), as multiple executions
* will just cause the inner scripts to be repeatedly cloned.
*/
JS_ASSERT(!bce->emittingRunOnceLambda);
bce->emittingRunOnceLambda = true;
if (!EmitTree(cx, bce, pn2))
return false;
bce->emittingRunOnceLambda = false;
callop = false;
break;
default:
if (!EmitTree(cx, bce, pn2))
return false;

View File

@ -114,6 +114,9 @@ struct BytecodeEmitter
bool emittingForInit:1; /* true while emitting init expr of for; exclude 'in' */
bool emittingRunOnceLambda:1; /* true while emitting a lambda which is only
expected to run once. */
const bool hasGlobalScope:1; /* frontend::CompileScript's scope chain is the
global object */
@ -153,6 +156,7 @@ struct BytecodeEmitter
return true;
}
bool isInLoop();
bool checkSingletonContext();
bool needsImplicitThis();

View File

@ -0,0 +1,11 @@
compare = (function() {
function inner() { return inner.caller; };
globalInner = inner;
globalClosure = inner();
return function(f) { return f === inner; }
})();
assertEq(compare(globalInner), true);
globalClosure();
assertEq(compare(globalInner), false);

View File

@ -1501,15 +1501,15 @@ js_CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent,
clone->initializeExtended();
}
if (cx->compartment == fun->compartment() && !types::UseNewTypeForClone(fun)) {
if (cx->compartment == fun->compartment() &&
!fun->hasSingletonType() &&
!types::UseNewTypeForClone(fun))
{
/*
* We can use the same type as the original function provided that (a)
* its prototype is correct, and (b) its type is not a singleton. The
* first case will hold in all compileAndGo code, and the second case
* will have been caught by CloneFunctionObject coming from function
* definitions or read barriers, so will not get here.
* Clone the function, reusing its script. We can use the same type as
* the original function provided that its prototype is correct.
*/
if (fun->getProto() == proto && !fun->hasSingletonType())
if (fun->getProto() == proto)
clone->setType(fun->type());
} else {
if (!JSObject::setSingletonType(cx, clone))

View File

@ -243,13 +243,22 @@ CloneFunctionObjectIfNotSingleton(JSContext *cx, HandleFunction fun, HandleObjec
* was called pessimistically, and we need to preserve the type's
* property that if it is singleton there is only a single object
* with its type in existence.
*
* For functions inner to run once lambda, it may be possible that
* the lambda runs multiple times and we repeatedly clone it. In these
* cases, fall through to CloneFunctionObject, which will deep clone
* the function's script.
*/
if (fun->hasSingletonType()) {
Rooted<JSObject*> obj(cx, SkipScopeParent(parent));
if (!JSObject::setParent(cx, fun, obj))
return NULL;
fun->setEnvironment(parent);
return fun;
RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
if (!script->hasBeenCloned) {
script->hasBeenCloned = true;
Rooted<JSObject*> obj(cx, SkipScopeParent(parent));
if (!JSObject::setParent(cx, fun, obj))
return NULL;
fun->setEnvironment(parent);
return fun;
}
}
return CloneFunctionObject(cx, fun, parent);

View File

@ -2477,7 +2477,7 @@ types::UseNewTypeForInitializer(JSContext *cx, HandleScript script, jsbytecode *
* arrays, but not normal arrays.
*/
if (!cx->typeInferenceEnabled() || script->function())
if (!cx->typeInferenceEnabled() || (script->function() && !script->treatAsRunOnce))
return false;
if (key != JSProto_Object && !(key >= JSProto_Int8Array && key <= JSProto_Uint8ClampedArray))
@ -4211,25 +4211,25 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferen
poppedTypes(pc, 0)->addArith(cx, script, pc, &pushed[0]);
break;
case JSOP_LAMBDA:
case JSOP_DEFFUN: {
case JSOP_LAMBDA: {
RootedObject obj(cx, script_->getObject(GET_UINT32_INDEX(pc)));
TypeSet *res = &pushed[0];
TypeSet *res = NULL;
if (op == JSOP_LAMBDA)
res = &pushed[0];
if (res) {
if (script_->compileAndGo && !UseNewTypeForClone(obj->toFunction()))
res->addType(cx, Type::ObjectType(obj));
else
res->addType(cx, Type::UnknownType());
} else {
cx->compartment->types.monitorBytecode(cx, script, offset);
}
// If the lambda may produce values with different types than the
// original function, despecialize the type produced here. This includes
// functions that are deep cloned at each lambda, as well as inner
// functions to run-once lambdas which may actually execute multiple times.
if (script_->compileAndGo && !script_->treatAsRunOnce && !UseNewTypeForClone(obj->toFunction()))
res->addType(cx, Type::ObjectType(obj));
else
res->addType(cx, Type::AnyObjectType());
break;
}
case JSOP_DEFFUN:
cx->compartment->types.monitorBytecode(cx, script, offset);
break;
case JSOP_DEFVAR:
break;
@ -4536,14 +4536,9 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferen
pushed[0].addType(cx, Type::UnknownType());
break;
case JSOP_CALLEE: {
JSFunction *fun = script_->function();
if (script_->compileAndGo && !UseNewTypeForClone(fun))
pushed[0].addType(cx, Type::ObjectType(fun));
else
pushed[0].addType(cx, Type::UnknownType());
case JSOP_CALLEE:
pushed[0].addType(cx, Type::AnyObjectType());
break;
}
default:
/* Display fine-grained debug information first */

View File

@ -474,6 +474,8 @@ class JSScript : public js::gc::Cell
undefined properties in this
script */
bool hasSingletons:1; /* script has singleton objects */
bool treatAsRunOnce:1; /* script is a lambda to treat as running once. */
bool hasBeenCloned:1; /* script has been reused for a clone. */
bool isActiveEval:1; /* script came from eval(), and is still active */
bool isCachedEval:1; /* script came from eval(), and is in eval cache */
bool uninlineable:1; /* script is considered uninlineable by analysis */