chromiumembedded / cef

Chromium Embedded Framework (CEF). A simple framework for embedding Chromium-based browsers in other applications.
https://bitbucket.org/chromiumembedded/cef/
Other
3.14k stars 452 forks source link

Support custom V8 snapshots #3734

Closed magreenblatt closed 1 week ago

magreenblatt commented 3 weeks ago

Is your feature request related to a problem? Please describe. Custom startup snapshots can be used to speed up V8/JavaScript load time in the renderer process. See also this example of how they are currently used with Electron.

Describe the solution you'd like

Additional context To support this capability a web application must provide a non-user-specific "ready state" that can be loaded for the purposes of creating the snapshot (e.g. library scripts that are loaded at startup, before the "actual" application runs). The generated snapshot is then distributed with the CEF application as an additional binary file. A new/separate snapshot will need to be created for each platform/architecture, each CEF/Chromium version, and each time the web application's "ready state" changes. Consequently, creation of snapshots should become part of the application build process or packaging step.

magreenblatt commented 2 weeks ago

Add tools/API to create the snapshot Add API to load the snapshot

  1. Generate a single JavaScript file that can be provided to the mksnapshot tool. For example, using electron-link.
  2. Run the mksnapshot tool (available as part of the CEF/Chromium build). This generates a v8_context_snapshot.bin file.
  3. Replace the default v8_context_snapshot.bin file in the app installation/bundle.
magreenblatt commented 2 weeks ago

Generate a single JavaScript file that can be provided to the mksnapshot tool.

  1. In a temp directory, install electron-link:
    % npm install --save electron-link
    added 44 packages in 21s
  2. Create a snapshot.js file with the modules that you want to bundle (install the modules first if necessary):
    require('react')
    require('react-dom')
  3. Create a run.js script to run electron-link (based on this example):
    const vm = require('vm')
    const path = require('path')
    const fs = require('fs')
    const electronLink = require('electron-link')
    const excludedModules = {}
    async function main () {
    const baseDirPath = __dirname
    console.log('Creating a linked script..')
    const result = await electronLink({
    baseDirPath: baseDirPath,
    mainPath: `${baseDirPath}/snapshot.js`,
    cachePath: `${baseDirPath}/cache`,
    shouldExcludeModule: (modulePath) => excludedModules.hasOwnProperty(modulePath)
    })
    const snapshotScriptPath = `${baseDirPath}/cache/snapshot.js`
    fs.writeFileSync(snapshotScriptPath, result.snapshotScript)
    // Verify if we will be able to use this in `mksnapshot`
    vm.runInNewContext(result.snapshotScript, undefined, {filename: snapshotScriptPath, displayErrors: true})
    }
    main().catch(err => console.error(err))
  4. Run the script:
    % node run.js
    Creating a linked script..
  5. Output is the cache/snapshot.js file.
    % ls -sh cache/snapshot.js    
    2880 cache/snapshot.js
magreenblatt commented 2 weeks ago

Run the mksnapshot tool

(The following instructions are based loosely on Electron's mksnapshot.js)

  1. Identify the original command-line that Chromium used to generate the snapshot (or this way):
    
    % cd ~/code/chromium_git/chromium/src

Find the target name.

% gn ls out/Debug_GN_arm64 | grep run_mksnapshot //v8:run_mksnapshot_default

Find the target outputs.

% gn outputs out/Debug_GN_arm64 //v8:run_mksnapshot_default gen/v8/embedded.S snapshot_blob.bin

Delete the output file so that it will be regenerated.

% rm -rf out/Debug_GN_arm64/snapshot_blob.bin

Run verbose to get the command-line.

% ninja -v -C out/Debug_GN_arm64 v8:run_mksnapshot_default
ninja: Entering directory `out/Debug_GN_arm64' [1/2] python3 ../../v8/tools/run.py ./mksnapshot --turbo_instruction_scheduling --stress-turbo-late-spilling --target_os=mac --target_arch=arm64 --embedded_src gen/v8/embedded.S --embedded_variant Default --random-seed 314159265 --startup_blob snapshot_blob.bin --native-code-counters --verify-heap

2. Returning to the temp directory, run the `mksnapshot` tool to generate `snapshot_blob.bin`:

% TOOL_PATH=~/code/chromium_git/chromium/src/out/Debug_GN_arm64 % $TOOL_PATH/mksnapshot --turbo_instruction_scheduling --stress-turbo-late-spilling --target_os=mac --target_arch=arm64 --embedded_src cache/embedded.S --embedded_variant Default --random-seed 314159265 --startup_blob cache/snapshot_blob.bin --native-code-counters --verify-heap cache/snapshot.js Loading script for embedding: cache/snapshot.js

3. Copy the `v8_context_snapshot_generator` tool to the cache directory (it must exist next to `snapshot_blob.bin`) and run to generate `v8_context_snapshot.arm64.bin` (platform/OS-specific name):

% cp -R $TOOL_PATH/v8_context_snapshot_generator cache/ % cd cache % ./v8_context_snapshot_generator --output_file=cache/v8_context_snapshot.arm64.bin

4. Verify the change in file size:

New files.

% ls -s cache/*.bin 9048 cache/snapshot_blob.bin 10080 cache/v8_context_snapshot.arm64.bin

Original (default) files.

% ls -s $TOOL_PATH/*.bin
2680 /Users/marshall/code/chromium_git/chromium/src/out/Debug_GN_arm64/snapshot_blob.bin 3368 /Users/marshall/code/chromium_git/chromium/src/out/Debug_GN_arm64/v8_context_snapshot.arm64.bin

magreenblatt commented 2 weeks ago

Replace the default v8_context_snapshot.bin file in the app installation/bundle.

  1. Copy the generated v8_context_snapshot.arm64.bin into the CEF app bundle:
    % APP_PATH=~/code/chromium_git/chromium/src/out/Debug_GN_arm64/
    % cp -R cache/v8_context_snapshot.arm64.bin $APP_PATH/cefclient.app/Contents/Frameworks/Chromium\ Embedded\ Framework.framework/Resources
  2. Run the app:
    % open $APP_PATH/cefclient.app
  3. Verify that the snapshot contents (snapshotResult global object) exist in DevTools:

image

  1. Override the default require function (example here) to use snapshot contents in a JavaScript application.
magreenblatt commented 2 weeks ago

CEF will need to provide the following:

  1. Instructions for using electron-link based on above.
  2. Bundled versions of mksnapshot and v8_context_snapshot_generator binary tools.
  3. Original command-line arguments passed to mksnapshot (platform/OS-specific).
  4. A script like Electron's to run the tools and generate the updated v8_context_snapshot.bin (with correct platform/OS-specific name).
magreenblatt commented 2 weeks ago

Identify the original command-line that Chromium used to generate the snapshot

This could also be extracted from out/Debug_GN_arm64/toolchain.ninja:

rule __v8_run_mksnapshot_default___build_toolchain_mac_clang_arm64__rule
  command = python3 ../../v8/tools/run.py ./mksnapshot --turbo_instruction_scheduling --stress-turbo-late-spilling --target_os=mac --target_arch=arm64 --embedded_src gen/v8/embedded.S --embedded_variant Default --random-seed 314159265 --startup_blob snapshot_blob.bin --native-code-counters --verify-heap
  description = ACTION //v8:run_mksnapshot_default(//build/toolchain/mac:clang_arm64)
magreenblatt commented 2 weeks ago

CEF will need to provide the following

How this might work with a CEF binary distribution (Mac ARM64 example):

  1. CEF binary distribution contains a tools/snapshot directory:
    % ls -s
    total 3068112
    190392 mksnapshot                8 run.sh
      8 mksnapshot_cmd.txt      2877704 v8_context_snapshot_generator
  2. User generates the snapshot.js file (like here) and copies it into that directory.
  3. User runs the provided run.sh script to generate v8_context_snapshot.arm64.bin:
    % ./run.sh snapshot.js 
    Running mksnapshot...
    Loading script for embedding: snapshot.js
    Running v8_context_snapshot_generator...
    Done
    % ls -s            
    total 3137344
    45184 embedded.S                8 run.sh                10080 v8_context_snapshot.arm64.bin
    190392 mksnapshot             2880 snapshot.js         2877704 v8_context_snapshot_generator
      8 mksnapshot_cmd.txt        11088 snapshot_blob.bin
  4. User copies v8_context_snapshot.arm64.bin into the app bundle or installer package (like here).

The mksnapshot_cmd.txt file contains the mksnapshot command-line:

--turbo_instruction_scheduling --stress-turbo-late-spilling --target_os=mac --target_arch=arm64 --embedded_src embedded.S --embedded_variant Default --random-seed 314159265 --startup_blob snapshot_blob.bin --native-code-counters --verify-heap

The run.sh file (or run.bat file on Windows) contains:

#!/bin/sh

# Read mksnapshot_cmd.txt into an argument array.
IFS=' ' read -r -a args < mksnapshot_cmd.txt

# Generate snapshot_blob.bin.
echo 'Running mksnapshot...'
./mksnapshot "${args[@]}" "$@"

# Determine the architecture suffix, if any.
suffix=''
if [ "$(uname)" == "Darwin" ]; then
  value='--target_arch=arm64'
  if [[ " ${args[*]} " =~ [[:space:]]${value}[[:space:]] ]]; then
    suffix='.arm64'
  else
    suffix='.x86_64'
  fi
fi

# Generate v8_context_snapshot.bin.
echo 'Running v8_context_snapshot_generator...'
./v8_context_snapshot_generator --output_file=v8_context_snapshot${suffix}.bin

echo 'Done.'
magreenblatt commented 2 weeks ago

Official builds of mksnapshot + v8_context_snapshot_generator are pretty large (174MB uncompressed on Mac ARM64), so we'll likely distribute them as a separate archive (e.g. cef_binary_[version]_[platform]_tools.tar.bz2)

magreenblatt commented 2 weeks ago

Cross-compile builds (e.g. ARM64 MacOS host building x64) place the tools in a subdirectory like:

% autoninja -C out/Debug_GN_x64 v8_context_snapshot
% ls -s out/Debug_GN_x64/clang_arm64_v8_x64
total 3598216
      0 angledata                100184 libvk_swiftshader.dylib
   2224 bytecode_builtins_list_generator          8 libvk_swiftshader.dylib.TOC
      0 gen                       0 local_rustc_sysroot
   5392 gen-regexp-special-case          194408 mksnapshot
  20448 icudtl.dat                    0 obj
      0 jsproto                       0 pyproto
  14616 libEGL.dylib                      0 resources
     16 libEGL.dylib.TOC               2688 snapshot_blob.bin
 129080 libGLESv2.dylib               23272 toolchain.ninja
    104 libGLESv2.dylib.TOC            7632 torque
  10872 libVkICD_mock_icd.dylib               8 v8_build_config.json
      8 libVkICD_mock_icd.dylib.TOC     2889848 v8_context_snapshot_generator
 197392 libVkLayer_khronos_validation.dylib       8 vk_swiftshader_icd.json
      8 libVkLayer_khronos_validation.dylib.TOC
% file out/Debug_GN_x64/clang_arm64_v8_x64/mksnapshot 
out/Debug_GN_x64/clang_arm64_v8_x64/mksnapshot: Mach-O 64-bit executable arm64

The final output file (v8_context_snapshot.x86_64.bin) is in the top-level out/Debug_GN_x64 directory.

Some build configurations will also have a V8 profile input file that needs to be included in the distribution.

magreenblatt commented 2 weeks ago

Note: use_v8_context_snapshot may be turned off by default in non-Official builds. See this blink-dev thread.

magreenblatt commented 1 week ago

On MacOS ARM64 it's necessary to run xattr -c <binary> to bypass security blocks after downloading the tools distribution.

magreenblatt commented 1 week ago

Looks like mksnapshot is crashing for MacOS x64 at M127 when specifying an additional script argument. Filed with Chromium as https://issues.chromium.org/issues/353552530

To debug mksnapshot crashes (MacOS x64 cross-compile in this example):

% cd ~/code/chromium_git/chromium/src/cef
% export CEF_ENABLE_AMD64=1
% export GN_OUT_CONFIGS=Debug_GN_x64
% export GN_DEFINES=is_official_build=true
% ./cef_create_projects.sh 
[...]
Done. Made 27138 targets from 3606 files in 4809ms
% cd ..
% time autoninja -C out/Debug_GN_x64 run_mksnapshot_default
ninja: Entering directory `out/Debug_GN_x64'
[3429/3429] STAMP obj/v8/run_mksnapshot_default.stamp
autoninja -C out/Debug_GN_x64 run_mksnapshot_default  6946.29s user 315.17s system 1376% cpu 8:47.63 total
# Get command-line arguments from mksnapshot_cmd.txt
% lldb -- ./out/Debug_GN_x64/clang_arm64_v8_x64/mksnapshot --turbo_instruction_scheduling --stress-turbo-late-spilling --target_os=mac --target_arch=x64 --embedded_src embedded.S --embedded_variant Default --random-seed 314159265 --startup_blob snapshot_blob.bin --no-native-code-counters ~/tmp/snapshot.js
[...]
(lldb) r
Process 12640 launched: '/Users/marshall/code/chromium_git/chromium/src/out/Debug_GN_x64/clang_arm64_v8_x64/mksnapshot' (arm64)
Loading script for embedding: /Users/marshall/tmp/snapshot.js
Process 12640 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=1, subcode=0xe5894855)
    frame #0: 0x000001e80eb49cc0
->  0x1e80eb49cc0: str    z21, [x2, #0x4a, mul vl]
    0x1e80eb49cc4: .long  0x8348026a                ; unknown opcode
    0x1e80eb49cc8: b.gt   0x1e80ebcbde4
    0x1e80eb49ccc: .long  0x56415541                ; unknown opcode
Target 0: (mksnapshot) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=1, subcode=0xe5894855)
  * frame #0: 0x000001e80eb49cc0
    frame #1: 0x000000010031797c mksnapshot`v8::internal::Execution::CallScript(isolate=<unavailable>, script_function=<unavailable>, receiver=<unavailable>, host_defined_options=Handle<v8::internal::Object> @ 0x000000016fde38c8) at execution.cc:517:10 [opt]
    frame #2: 0x0000000100043f28 mksnapshot`v8::Script::Run(this=<unavailable>, context=<unavailable>, host_defined_options=<unavailable>) at api.cc:2115:7 [opt]
    frame #3: 0x0000000100c4925c mksnapshot`v8::internal::(anonymous namespace)::RunExtraCode(isolate=<unavailable>, context=<unavailable>, utf8_source=<unavailable>, name=<unavailable>) at snapshot.cc:779:15 [opt]
    frame #4: 0x0000000100c49050 mksnapshot`v8::internal::CreateSnapshotDataBlobInternal(function_code_handling=<unavailable>, embedded_source=<unavailable>, snapshot_creator=<unavailable>, serializer_flags=<unavailable>) at snapshot.cc:797:10 [opt]
    frame #5: 0x000000010002e14c mksnapshot`main [inlined] (anonymous namespace)::CreateSnapshotDataBlob(snapshot_creator=0x000000016fdff150, embedded_source=<unavailable>) at mksnapshot.cc:162:28 [opt]
    frame #6: 0x000000010002e118 mksnapshot`main(argc=2, argv=<unavailable>) at mksnapshot.cc:295:16 [opt]