Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom V8 snapshots #3734

Closed
magreenblatt opened this issue Jul 8, 2024 · 12 comments
Closed

Support custom V8 snapshots #3734

magreenblatt opened this issue Jul 8, 2024 · 12 comments
Labels
enhancement Enhancement request

Comments

@magreenblatt
Copy link
Collaborator

magreenblatt commented Jul 8, 2024

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

  • Add tools/API to create the snapshot (must run on each supported platform/architecture)
  • Add API to load the snapshot

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 magreenblatt added the enhancement Enhancement request label Jul 8, 2024
@magreenblatt
Copy link
Collaborator Author

magreenblatt commented Jul 9, 2024

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
Copy link
Collaborator Author

magreenblatt commented Jul 9, 2024

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
  1. Create a snapshot.js file with the modules that you want to bundle (install the modules first if necessary):
require('react')
require('react-dom')
  1. 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))
  1. Run the script:
% node run.js
Creating a linked script..
  1. Output is the cache/snapshot.js file.
% ls -sh cache/snapshot.js    
2880 cache/snapshot.js

@magreenblatt
Copy link
Collaborator Author

magreenblatt commented Jul 9, 2024

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
  1. 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
  1. 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
  1. 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
Copy link
Collaborator Author

magreenblatt commented Jul 9, 2024

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
  1. Run the app:
% open $APP_PATH/cefclient.app
  1. Verify that the snapshot contents (snapshotResult global object) exist in DevTools:

image
4. Override the default require function (example here) to use snapshot contents in a JavaScript application.

@magreenblatt
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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
Copy link
Collaborator Author

magreenblatt commented Jul 9, 2024

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
  1. User generates the snapshot.js file (like here) and copies it into that directory.
  2. 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
  1. 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
Copy link
Collaborator Author

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
Copy link
Collaborator Author

magreenblatt commented Jul 11, 2024

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
Copy link
Collaborator Author

magreenblatt commented Jul 15, 2024

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

@magreenblatt
Copy link
Collaborator Author

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

@magreenblatt
Copy link
Collaborator Author

magreenblatt commented Jul 16, 2024

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]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Enhancement request
Projects
None yet
Development

No branches or pull requests

1 participant