diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index c300c4ec5fc..9137bb7a707 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -242,6 +242,58 @@ JSRuntime::createJaegerRuntime(JSContext *cx) } #endif +void +JSCompartment::sweepCallsiteClones() +{ + if (callsiteClones.initialized()) { + for (CallsiteCloneTable::Enum e(callsiteClones); !e.empty(); e.popFront()) { + CallsiteCloneKey key = e.front().key; + JSFunction *fun = e.front().value; + if (!key.script->isMarked() || !fun->isMarked()) + e.removeFront(); + } + } +} + +RawFunction +js::CloneFunctionAtCallsite(JSContext *cx, HandleFunction fun, HandleScript script, jsbytecode *pc) +{ + JS_ASSERT(cx->typeInferenceEnabled()); + JS_ASSERT(fun->isCloneAtCallsite()); + JS_ASSERT(types::UseNewTypeForClone(fun)); + JS_ASSERT(!fun->nonLazyScript()->enclosingStaticScope()); + + typedef CallsiteCloneKey Key; + typedef CallsiteCloneTable Table; + + Table &table = cx->compartment->callsiteClones; + if (!table.initialized() && !table.init()) + return NULL; + + Key key; + key.script = script; + key.offset = pc - script->code; + key.original = fun; + + Table::AddPtr p = table.lookupForAdd(key); + if (p) + return p->value; + + RootedObject parent(cx, fun->environment()); + RootedFunction clone(cx, CloneFunctionObject(cx, fun, parent, + JSFunction::ExtendedFinalizeKind)); + if (!clone) + return NULL; + + // Store a link back to the original for function.caller. + clone->setExtendedSlot(0, ObjectValue(*fun)); + + if (!table.add(p, key, clone.get())) + return NULL; + + return clone; +} + JSContext * js::NewContext(JSRuntime *rt, size_t stackChunkSize) { diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index e78324bf3d8..c81bd910754 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -58,6 +58,39 @@ namespace js { typedef HashSet ObjectSet; +struct CallsiteCloneKey { + /* The original function that we are cloning. */ + JSFunction *original; + + /* The script of the call. */ + JSScript *script; + + /* The offset of the call. */ + uint32_t offset; + + CallsiteCloneKey() { PodZero(this); } + + typedef CallsiteCloneKey Lookup; + + static inline uint32_t hash(CallsiteCloneKey key) { + return uint32_t(size_t(key.script->code + key.offset) ^ size_t(key.original)); + } + + static inline bool match(const CallsiteCloneKey &a, const CallsiteCloneKey &b) { + return a.script == b.script && a.offset == b.offset && a.original == b.original; + } +}; + +typedef HashMap, + CallsiteCloneKey, + SystemAllocPolicy> CallsiteCloneTable; + +RawFunction CloneFunctionAtCallsite(JSContext *cx, HandleFunction fun, + HandleScript script, jsbytecode *pc); + +typedef HashSet ObjectSet; + /* Detects cycles when traversing an object graph. */ class AutoCycleDetector { diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 756b11463e9..cd3a0d62396 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -650,6 +650,7 @@ JSCompartment::sweep(FreeOp *fop, bool releaseTypes) sweepNewTypeObjectTable(newTypeObjects); sweepNewTypeObjectTable(lazyTypeObjects); sweepBreakpoints(fop); + sweepCallsiteClones(); if (global_ && IsObjectAboutToBeFinalized(global_.unsafeGet())) global_ = NULL; diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 39f75b983e7..53ef4037673 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -349,6 +349,14 @@ struct JSCompartment : private JS::shadow::Compartment, public js::gc::GraphNode js::types::TypeObject *getLazyType(JSContext *cx, js::Handle proto); + /* + * Hash table of all manually call site-cloned functions from within + * self-hosted code. Cloning according to call site provides extra + * sensitivity for type specialization and inlining. + */ + js::CallsiteCloneTable callsiteClones; + void sweepCallsiteClones(); + /* * Keeps track of the total number of malloc bytes connected to a * compartment's GC things. This counter should be used in preference to diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 69375e651a2..4d4407b1402 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -84,6 +84,13 @@ fun_getProperty(JSContext *cx, HandleObject obj_, HandleId id, MutableHandleValu } RootedFunction fun(cx, obj->toFunction()); + /* + * Callsite clones should never escape to script, so get the original + * function. + */ + if (fun->isCallsiteClone()) + fun = fun->getExtendedSlot(0).toObject().toFunction(); + /* * Mark the function's script as uninlineable, to expand any of its * frames on the stack before we go looking for them. This allows the @@ -1514,29 +1521,8 @@ js_CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent, * (JS_CloneFunctionObject) which dynamically ensures that 'script' has * no enclosing lexical scope (only the global scope). */ - if (clone->isInterpreted()) { - RootedScript script(cx, clone->nonLazyScript()); - JS_ASSERT(script->compartment() == fun->compartment()); - JS_ASSERT_IF(script->compartment() != cx->compartment, - !script->enclosingStaticScope()); - - RootedObject scope(cx, script->enclosingStaticScope()); - - clone->mutableScript().init(NULL); - - RootedScript cscript(cx, CloneScript(cx, scope, clone, script)); - if (!cscript) - return NULL; - - clone->setScript(cscript); - cscript->setFunction(clone); - - GlobalObject *global = script->compileAndGo ? &script->global() : NULL; - - script = clone->nonLazyScript(); - CallNewScriptHook(cx, script, clone); - Debugger::onNewScript(cx, script, global); - } + if (clone->isInterpreted() && !CloneFunctionScript(cx, fun, clone)) + return NULL; } return clone; } diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 7e56776c883..051c2529957 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -44,6 +44,13 @@ class JSFunction : public JSObject HAS_DEFAULTS = 0x0800, /* function has at least one default parameter */ INTERPRETED_LAZY = 0x1000, /* function is interpreted but doesn't have a script yet */ + /* + * Function is cloned anew at each callsite. This is temporarily + * needed for ParallelArray selfhosted code until type information can + * be made context sensitive. See discussion in bug 826148. + */ + CALLSITE_CLONE = 0x2000, + /* Derived Flags values for convenience: */ NATIVE_FUN = 0, INTERPRETED_LAMBDA = INTERPRETED | LAMBDA @@ -100,6 +107,11 @@ class JSFunction : public JSObject bool hasRest() const { return flags & HAS_REST; } bool hasDefaults() const { return flags & HAS_DEFAULTS; } + /* Original functions that should be cloned are not extended. */ + bool isCloneAtCallsite() const { return (flags & CALLSITE_CLONE) && !isExtended(); } + /* Cloned functions keep a backlink to the original in extended slot 0. */ + bool isCallsiteClone() const { return (flags & CALLSITE_CLONE) && isExtended(); } + /* Compound attributes: */ bool isBuiltin() const { return isNative() || isSelfHostedBuiltin(); @@ -141,6 +153,10 @@ class JSFunction : public JSObject flags |= SELF_HOSTED_CTOR; } + void setIsCloneAtCallsite() { + flags |= CALLSITE_CLONE; + } + void setIsFunctionPrototype() { JS_ASSERT(!isFunctionPrototype()); flags |= IS_FUN_PROTO; diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 121a4973228..5229668c055 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -2359,8 +2359,25 @@ BEGIN_CASE(JSOP_FUNCALL) bool construct = (*regs.pc == JSOP_NEW); RootedFunction &fun = rootFunction0; + bool isFunction = IsFunctionObject(args.calleev(), fun.address()); + + /* + * Some builtins are marked as clone-at-callsite to increase precision of + * TI and JITs. + */ + if (isFunction) { + if (fun->isInterpretedLazy() && !JSFunction::getOrCreateScript(cx, fun)) + goto error; + if (cx->typeInferenceEnabled() && fun->isCloneAtCallsite()) { + fun = CloneFunctionAtCallsite(cx, fun, script, regs.pc); + if (!fun) + goto error; + args.setCallee(ObjectValue(*fun)); + } + } + /* Don't bother trying to fast-path calls to scripted non-constructors. */ - if (!IsFunctionObject(args.calleev(), fun.address()) || !fun->isInterpretedConstructor()) { + if (!isFunction || !fun->isInterpretedConstructor()) { if (construct) { if (!InvokeConstructorKernel(cx, args)) goto error; @@ -2380,9 +2397,7 @@ BEGIN_CASE(JSOP_FUNCALL) InitialFrameFlags initial = construct ? INITIAL_CONSTRUCT : INITIAL_NONE; bool newType = cx->typeInferenceEnabled() && UseNewType(cx, script, regs.pc); - RootedScript funScript(cx, JSFunction::getOrCreateScript(cx, fun)); - if (!funScript) - goto error; + RootedScript funScript(cx, fun->nonLazyScript()); if (!cx->stack.pushInlineFrame(cx, regs, args, *fun, funScript, initial)) goto error; diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 1a8f6e748b9..318f7e12ba8 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -2343,6 +2343,37 @@ js::CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, return dst; } +bool +js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone) +{ + JS_ASSERT(clone->isInterpreted()); + + RootedScript script(cx, clone->nonLazyScript()); + JS_ASSERT(script); + JS_ASSERT(script->compartment() == original->compartment()); + JS_ASSERT_IF(script->compartment() != cx->compartment, + !script->enclosingStaticScope()); + + RootedObject scope(cx, script->enclosingStaticScope()); + + clone->mutableScript().init(NULL); + + RawScript cscript = CloneScript(cx, scope, clone, script); + if (!cscript) + return false; + + clone->setScript(cscript); + cscript->setFunction(clone); + + GlobalObject *global = script->compileAndGo ? &script->global() : NULL; + + script = clone->nonLazyScript(); + CallNewScriptHook(cx, script, clone); + Debugger::onNewScript(cx, script, global); + + return true; +} + DebugScript * JSScript::debugScript() { diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 011eabbfd86..abbdab94b9c 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1300,6 +1300,9 @@ CurrentScriptFileLineOrigin(JSContext *cx, unsigned *linenop, LineOption = NOT_C extern UnrootedScript CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript script); +bool +CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone); + /* * NB: after a successful XDR_DECODE, XDRScript callers must do any required * subsequent set-up of owning function or script object and then call