There are several flavors of JavaScript function. All are JSObject
s of the same class, js_FunctionClass
.
(But note that objects of other classes can be callable and can even have typeof obj == "function"
.)
Script functions
Functions written in JavaScript and compiled to bytecode. There are four variations that affect how NameExpressions are evaluated. (NameExpressions are basic expressions like String
and x
that would eat up a huge amount of run time if the engine weren't smart enough to avoid symbol table lookups.)
General closures are the base case. When the function object is created, its parent is set to the first object on the scope chain. When a name is evaluated that doesn't refer to a local variable, the interpreter consults the scope chain to find the variable. When with
or eval
are used, we have to do this for correctness.
This is slow, not only because walking the scope chain is a drag, but also because we'd rather avoid actually creating the scope chain at all, if possible. General closures force the interpreter to verify the whole scope chain. So we optimize this when we can.
These optimizations depend on being sure that we will never have to walk the scope chain, so with
and eval
inhibit them all.
Null closures. If we can prove at compile time that a function does not refer to any locals or arguments of enclosing functions, it is a null closure. Since it will never need to walk the scope chain, its parent is the global object. This is the best case. Barring with
and eval
, all outermost functions are null closures.
Null closures with upvars. A nested function is algol-like if it is only ever defined and called, and it isn't accessed in any other way (and it is not a generator-function). Such a function is guaranteed never to be called again after the enclosing function exits. An algol-like function may read the local variables and arguments of its immediate enclosing function from the stack, as if by magic. (JSContext::display
caches the enclosing function's stack frame.) If that function is also algol-like, its child can read locals and variables from the next enclosing function, and so on. The compiler detects these cases and makes such functions null closures too.
Flat closures. Suppose a function reads some variables from enclosing functions but is not algol-like. If the function does not assign to any closed-on vars/args, and it only reads closed-on local variables and arguments that never change value after the function is created, then the function can be implemented as a flat closure. When a flat closure is created, all the closed-on values are copied from the stack into reserved slots of the function object. To evaluate a name, instead of walking the scope chain, we just take the value from the reserved slot. The function object's parent is the global object.
Flat closures and Null closures have been removed:
https://bugzilla.mozilla.org/show_bug.cgi?id=730497
https://bugzilla.mozilla.org/show_bug.cgi?id=739808
Name lookups
In order of speed, fastest to slowest.
-
ARG
andLOCAL
instructions. If a name definitely refers to an argument or local variable of the immediately enclosing function, it can be accessed usingJSOP_{GET,SET,CALL}
{ARG,LOCAL}
instructions. Some of these can even be fused with other operations into combo instructions likeJSOP_GETARGPROP
,JSOP_FORLOCAL
, andJSOP_INCLOCAL
. Because arguments and locals can't be deleted, this optimization is available to all functions, andeval
does not interfere with it. But awith
block can:function f(s) { eval(s); print(s); // s can be loaded with GETARG with (obj) { print(s); // no GETARG here; s might refer to obj.s } }
-
UPVAR
instructions. JSOP_{GET,CALL}UPVAR (in algol-like functions only, I think?) TODO -
DSLOT
instructions. JSOP_{GET,CALL}DSLOT (in flat closures only) TODO -
GVAR
instructions. Outside all functions, if a name definitely refers to a global for which we have seen a var,const
, orfunction
declaration, then we emit a JS_DEFVAR instruction in the script prelude and access the global usingJSOP_{GET,SET,CALL}GVAR
. This is fast if the global either doesn't exist before the script runs (the script creates it) or it's a non-configurable data property (which amounts to the same thing). Otherwise theGVAR
instructions are as slow asNAME
instructions.There are also combo instructions
JSOP_{INC,DEC}GVAR
andJSOP_GVAR{INC,DEC}
. -
NAME
instructions. In the worst case we emitJSOP_{,SET,CALL}NAME
. These are totally general. They can still be optimized via the property cache; in the worst case we walk the scope chain.In some cases, the JIT can optimize a
JSOP_NAME
instruction that refers to a variable in an enclosing scope to pull the value directly out of theCall
object'sdslots
.For the expression
delete name
we always emitJSOP_DELNAME
. It's not worth optimizing.