javascriptv8

Does V8 optimize inner functions based on closure values?


For example:

function makeFunc(a,b,c,d,e) {
    return () => {
        if (a) { /* do something expensive not referencing b,c,d,e */ }
        if (b) { /* do something expensive not referencing a,c,d,e */ }
        if (c) { /* do something expensive not referencing a,b,d,e */ }
        if (d) { /* do something expensive not referencing a,b,c,e */ }
        if (e) { /* do something expensive not referencing a,b,c,d */ }
    }
}

const func = makeFunc(true, false, false, false, false)
for ( let i=0; i < 100_000; i++) func()

I'm hoping that, in the example, V8 would optimize away the if (x) tests on the closure, emitting just the

/* do something expensive not referencing b,c,d,e */

after if (a) to be performed 100,000 times.


Solution

  • Yes, V8 optimizes this, as long as the variables from the outer context are never written to (meaning: as long as no code exists that could ever write to them).

    Here is a counter-example where the optimization can't happen because the context variables aren't constants:

    function makeFuncs(a, b, c, d e) {
      return {
        f1: () => { if (a) { console.log("a was true"); }},
        f2: () => { a = !a; }
      }
    }
    
    let funcs = makeFuncs(true);
    funcs.f1();  // prints
    funcs.f2();
    funcs.f1();  // doesn't print
    

    Clearly, when optimizing f1 on its own, the compiler can't just take the current value of a and assume that it'll never change.

    But as long as f2 with its a = ... assignment doesn't exist, V8 realizes that a is never written to, so the compiler can safely consider it to be constant. So in the OP's example, the if checks do get optimized out.

    (Thanks to @Dada for doing most of the investigation here!)