Hi Andy,
I had a conversation with Geoff the other day about the block scope work, and I
thought of some useful formulations of performance constraints on the design.
Folks have been telling you a lot about how to implement, I am hoping some of
these rules can readily be turned into comparison benchmarks to test if a patch
meets the goal. Here is the most basic goal:
Constraint 1) Consider a function vf1() that contains no closures and uses var
in a correctly block-scoped manner;
==> If you replace all use of var with let in function vf1() to create
function lf1(), then lf1() should not suffer any speed or memory penalty
compared to vf1()
Constraint 1 implies that let-containing blocks should never create activations
if the function does not contain closures, among other things, and you can turn
whether you did this right into a test
Now more advanced constraints:
Now more advanced constraints:
Constraint 3) Consider a function vf2() that uses var in a correctly
block-scoped manner, and which contains a nested function expression, which may
or may not be reached depending on parameters to vf2():
==> If you replace all use of var with let in function vf2() to create
function lf2(), then lf2() should not suffer any speed or memory penalty
compared to vf2() *in the case where the closure is not actually created*
This implies lazy creation of activations.
Constraint 3) Consider a function vf3() that uses var in a correctly
block-scoped manner, and which contains a closure that is reachable only once
per call (i.e. not in a loop):
==> If you replace all use of var with let in function vf3() to create
function lf3(), then lf3() should not suffer any speed or memory penalty
compared to vf3()
This implies merging function and block activations when possible.
I think these constraints represent reasonable author expectations about use of
let, especially constraint 1. You should be able to use it as just a "better
var" without a performance penalty, unless you are using it in a way that
imposes a relevant semantic difference. I bet the main JSC hackers could
probably provide other similar constraint statements that express their
performance goals for let in an unambiguous way.
I suspect that it might be ok for the initial patch to only meet a subset of
such constraints.
Regards,
Maciej
On Mar 21, 2012, at 3:32 AM, Andy Wingo wrote:
> Hi,
>
> I have been plugging away at the block scope stuff. My current work is
> on https://bugs.webkit.org/show_bug.cgi?id=74708. Like some other
> patches, it uses catch clauses as the proof of concept, because there
> are so many tests around that area. It does lazy creation and tear-off
> if possible, and only in the case in which a scope has captured
> variables.
>
> I have two remaining things to fix before it is fully baked.
>
> One is static lookup of free variables (e.g. ResolveResult::Lexical
> variables). The issue is that with lazy scope creation ("reification")
> the depth in the scope chain cannot be computed statically. Let's say
> you are resolving "foo" in:
>
> (function () {
> var foo = 1;
> return function () {
> var bar = 3;
> function qux(x) { return bar+x; }
> return bar + foo;
> }
> })()
>
> Here, both functions will lazily create their activations. (I put in
> the qux to make sure the inner function has a lazy activation.) Here is
> their bytecode:
>
> Outer:
> 32 m_instructions; 256 bytes at 0xb10cf0; 1 parameter(s); 12 callee
> register(s); 4 variable(s)
>
> [ 0] enter
> [ 1] init_lazy_reg r0
> [ 3] init_lazy_reg r2
> [ 5] init_lazy_reg r1
> [ 7] mov r3, Int32: 1(@k0)
> [ 10] create_activation r0
> [ 12] new_func_exp r4, f0
> [ 15] mov r5, Undefined(@k1)
> [ 18] call r4, 1, 12
> [ 24] op_call_put_result r4
> [ 27] tear_off_activation r0, r2
> [ 30] ret r4
>
> Constants:
> k0 = Int32: 1
> k1 = Undefined
>
> Inner:
> 27 m_instructions; 216 bytes at 0xb0c930; 1 parameter(s); 6 callee
> register(s); 5 variable(s)
>
> [ 0] enter
> [ 1] init_lazy_reg r0
> [ 3] init_lazy_reg r2
> [ 5] init_lazy_reg r1
> [ 7] init_lazy_reg r4
> [ 9] mov r3, Int32: 3(@k0)
> [ 12] get_scoped_var r5, 3, 1
> [ 17] add r5, r3, r5
> [ 22] tear_off_activation r0, r2
> [ 25] ret r5
>
> Constants:
> k0 = Int32: 3
>
> Note the get_scoped_var in the inner function, corresponding to the
> resolution of "foo". It indicates that "foo" can be found in the third
> register of the scope object one scope deep. However there might be an
> additional object on the scope chain, corresponding to the lazily
> created activation of the inner function.
>
> There is explicit code to account for skipping over an activation in the
> implementations of put_scoped_var and get_scoped_var. But with more
> lazily reified scopes that may or may not be on the scope chain
> (depending on whether the code path traversed a with, eval, or function
> expression), such an approach doesn't work. You can't determine the
> static depth from the top of the scope chain, because you'd like to
> avoid creating the entire scope chain if you can.
>
> I suggest that we change the users of scope chains (eval, with, and
> function expressions) to receive the scope chain register as a
> parameter. The compiler already has to handle lazy scope creation, so
> it's just as easy to put the scope in a temporary as it is to put it in
> the callframe.
>
> Free variable lookup will continue to use the register in the
> callframe. As a bonus, that code can be simpler because it doesn't have
> to check if the there is an activation or not.
>
> OK, that's one.
>
>
> The second issue is handling temporal dead zone errors. In ES6 it is a
> SyntaxError if you access the value of a let- or const-bound variable
> before it is initialized. In practice this means that in the general
> case, we mark uninitialized variables with a sentinel value (JSValue(),
> via init_lazy_reg). Access to let- or const-bound variables that need a
> barrier is preceded by a check that the variable is initialized. We can
> prove for some cases that no barrier is needed, and elide the barrier,
> but OK.
>
> My question is how to structure this check. Currently I have an
> assert_lazy_reg_init opcode that throws an error if the value
> isEmpty(). I guess we need to associate a name with that error too, so
> that opcode should take an identifier as a parameter as well. WDYT?
>
> Currently there is also an assert_lazy_reg_not_init opcode, but that was
> based on a misunderstanding -- you can only initialize const variables,
> not assign to them. Hence initialization doesn't need such a check.
>
>
> Finally, two more thoughts. One is that we can use op_reify_scope /
> op_tear_off_scope for parameters and locals in some cases, if a function
> does not have non-strict eval, as Gavin suggested. Also I hope to get
> a patch for "const" in today. The break-the-web aspect is a bit
> tricky, but we'll see how that goes.
>
> Thanks for comments!
>
> Andy
> --
> http://wingolog.org/
> _______________________________________________
> squirrelfish-dev mailing list
> [email protected]
> http://lists.webkit.org/mailman/listinfo.cgi/squirrelfish-dev
_______________________________________________
squirrelfish-dev mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/squirrelfish-dev