Search code examples
javascriptandroidv8escape-analysisahead-of-time-compile

Can V8 perform build-time precompilation of JS code?


We're trying to optimize for start-up time of JS code on mobile and looking for the opportunities. I've found Facebook Hermes JS engine created for a similar purpose but we heavily depend on V8 at the moment.

Can build-time precompilation be done with V8 meaning parsing and code optimization will be done in compile-time and saving some time in runtime? Generating LLVM bitcode from the source code and executing bitcode in runtime seems to be pretty close to what i imagine. WASM seems to be not an option (at least for mobile).

If it's possible, can one provide a simple example of trivial JS code optimized with V8?

PS. Probably it would also help with memory consumption which can be the secondary goal.


Solution

  • V8 supports heap snapshots for this very purpose. It's used by the Atom editor, for instance, to improve startup time. It's not so much about precompiling as it is about prebuilding your global environment and instantiating your functions (which may not be compiled [yet], just converted to bytecode for Ignition, which is sufficient). If you're using Electron, the mksnapshot npm package may be useful. (And if not, looking at how it works may still be useful.)

    I haven't done any V8 hacking, but the example they link from the blog post above is as follows:

    TEST(PerIsolateSnapshotBlobs) {
      DisableTurbofan();
      const char* source1 = "function f() { return 42; }";
      const char* source2 =
          "function f() { return g() * 2; }"
          "function g() { return 43; }"
          "/./.test('a')";
      v8::StartupData data1 = v8::V8::CreateSnapshotDataBlob(source1);
      v8::StartupData data2 = v8::V8::CreateSnapshotDataBlob(source2);
      v8::Isolate::CreateParams params1;
      params1.snapshot_blob = &data1;
      params1.array_buffer_allocator = CcTest::array_buffer_allocator();
      v8::Isolate* isolate1 = v8::Isolate::New(params1);
      {
        v8::Isolate::Scope i_scope(isolate1);
        v8::HandleScope h_scope(isolate1);
        v8::Local<v8::Context> context = v8::Context::New(isolate1);
        delete[] data1.data;  // We can dispose of the snapshot blob now.
        v8::Context::Scope c_scope(context);
        CHECK_EQ(42, CompileRun("f()")->ToInt32(isolate1)->Int32Value());
        CHECK(CompileRun("this.g")->IsUndefined());
      }
      isolate1->Dispose();
      v8::Isolate::CreateParams params2;
      params2.snapshot_blob = &data2;
      params2.array_buffer_allocator = CcTest::array_buffer_allocator();
      v8::Isolate* isolate2 = v8::Isolate::New(params2);
      {
        v8::Isolate::Scope i_scope(isolate2);
        v8::HandleScope h_scope(isolate2);
        v8::Local<v8::Context> context = v8::Context::New(isolate2);
        delete[] data2.data;  // We can dispose of the snapshot blob now.
        v8::Context::Scope c_scope(context);
        CHECK_EQ(86, CompileRun("f()")->ToInt32(isolate2)->Int32Value());
        CHECK_EQ(43, CompileRun("g()")->ToInt32(isolate2)->Int32Value());
      }
      isolate2->Dispose();
    }
    

    That blog post (and the associated example?) is from 2015 so things have probably moved on since.