reuse CallObjects when nested functions don't escape




7 years ago
5 years ago


(Reporter: luke, Unassigned)


Firefox Tracking Flags

(Not tracked)


(Whiteboard: [js:t])



7 years ago
Given a function:

  function outer(x) {
    function inner() { return x }

we mark outer as 'heavyweight' since it contains a nested function that accesses the formal parameter 'x'.  However, since 'inner' doesn't escape, we shouldn't need to allocate a CallObject every time we call 'outer'.

This is the same observation as the "Algol display optimization" that was removed in pieces over the last two years.  The original approach used a frontend (parser) analysis and required complexity in the rest of the VM as well as special support in the jits.

With the new scope machinery I think we could use a simpler strategy to achieve the same effect:
 - when we analyze a heavyweight function, we check if any of the nested functions escape using the SSA we already have. if none of the nested functions escape, set a JSScript flag "canReuseScopeAfterEpilogue"
 - add a 'CallObject *JSScript::freeScope' field, initially NULL
 - in the heavyweight prologue when canReuseScopeAfterEpilogue, create a CallObject if freeScope is NULL, otherwise, reuse the one in freeScope and set freeScope to NULL
 - in the heavyweight epilogue where canReuseScopeAfterEpilogue, set freeScope = fp->scopeChain.

Thus, the only real change would be small modifications to StackFrame::prologue/epilogue (and their jit equivalents).

A few details:
 - we'd need a reliable way to know "this heavyweight function is only heavyweight b/c of nested functions". (I think it's (heavyweight && !bindingsAccessedDynamically && !funHasExtensibleScope), but I could be wrong; it'd be nice to stop with the name "heavyweight" and use flags with more specific meanings.)
 - we'd need a reliable way to find all nested functions (is that just all functions in script->objects?  perhaps instead we'd scan the bytecode for JSOP_LAMBDA?)
 - it would be nice to leverage an existing escape analysis based on IM which took advantage of inlining (there isn't one yet, but there may be one day).  Going further: if IM inlined all nested functions, there would be no need for a CallObject at all and all ALIASEDVAR opcodes could be turned into LOCAL opcodes...

(I'm not planning to do this anytime soon, just filing to collect use cases if they arise.)
Whiteboard: [js:t]
Assignee: general → nobody
You need to log in before you can comment on or make changes to this bug.