rusty_v8

repository·main·Indexed 26 days ago

https://github.com/denoland/rusty_v8

High-quality Rust bindings to the V8 C++ API, designed to match the original API closely without introducing additional call overhead. Used as a core component of the Deno project. Version 150.4.0.

Tokens
13.3K
Snippets
25
Records
108
Agent score
87%

What's inside rusty_v8

  1. Build rusty_v8 using prebuilt binaries

    main

    By default, cargo build will attempt to download and use prebuilt static libraries for V8 from GitHub releases to save compilation time. This is the fastest way to use the crate.

    To disable this behavior and force a build from source, set the V8_FROM_SOURCE environment variable to 1.

    cargo build
  2. Use a local mirror for V8 binaries

    main

    To avoid re-downloading V8 binaries, you can set up a local mirror. This is useful for CI or offline environments.

    1. Set RUSTY_V8_MIRROR to a local directory.
    2. Populate the directory with the required .a files following the versioned structure.

    Example setup:

    export RUSTY_V8_MIRROR=$HOME/.cache/rusty_v8
    
    # Populate the cache (example script)
    for REL in v0.13.0 v0.12.0; do
      mkdir -p $RUSTY_V8_MIRROR/$REL
      for FILE in \
        librusty_v8_debug_x86_64-unknown-linux-gnu.a \
        librusty_v8_release_x86_64-unknown-linux-gnu.a \
      ; do
        if [ ! -f $RUSTY_V8_MIRROR/$REL/$FILE ]; then
          wget -O $RUSTY_V8_MIRROR/$REL/$FILE \
            https://github.com/denoland/rusty_v8/releases/download/$REL/$FILE
        fi
      done
    done
  3. Build V8 from source

    main

    To build the crate completely from source code, use the V8_FROM_SOURCE=1 environment variable. This is required if you want to use experimental features or if you are making changes to the rusty_v8 bindings themselves.

    Prerequisites

    • Python 3: Must be available as python3 in your PATH. Use the PYTHON environment variable to specify a specific binary.
    • curl: Must be installed on your system.
    • libclang: Requires libclang 19+ for bindgen.
    • Linux: Requires glib-2.0 development files (e.g., sudo apt install libglib2.0-dev on Ubuntu).
    • macOS: Requires Xcode and Xcode CLT. You may need to set PYTHON=python3.
    • Windows: Requires the 64-bit toolchain (32-bit is not supported).

    Build Commands

    Linux

    # Install libclang and set path
    sudo apt install libclang-19-dev
    export LIBCLANG_PATH=/usr/lib/llvm-19/lib
    
    # Build from source
    V8_FROM_SOURCE=1 cargo build -vv

    Android (Cross-compilation)

    # Using rustup
    rustup target add aarch64-linux-android
    V8_FROM_SOURCE=1 cargo build -vv --target aarch64-linux-android
    
    # Using cross
    docker build --build-arg CROSS_BASE_IMAGE=ghcr.io/cross-rs/aarch64-linux-android:0.2.5 -t cross-rusty_v8:aarch64-linux-android .
    V8_FROM_SOURCE=1 cross build -vv --target aarch64-linux-android

    iOS (Cross-compilation from macOS)

    # Simulator target (supports JIT)
    rustup target add aarch64-apple-ios-sim
    V8_FROM_SOURCE=1 cargo build -vv --target aarch64-apple-ios-sim
    
    # Device target (jitless, WebAssembly disabled)
    rustup target add aarch64-apple-ios
    V8_FROM_SOURCE=1 cargo build -vv --target aarch64-apple-ios
  4. Enable experimental V8 Sandbox feature

    main

    The v8_enable_sandbox feature provides improved safety for executing potentially malicious JavaScript via memory cages.

    Note: This feature is experimental, not CI-tested, and requires either V8_FROM_SOURCE=1 or a custom-built archive. Enabling the sandbox implies pointer compression is also enabled and may increase overhead or API limitations. It also allocates approximately 1TB of virtual memory.

  5. Initialize the V8 engine

    main

    To use V8, you must follow a specific initialization sequence. You must first initialize the platform, then initialize V8 itself. V8 must be initialized before any Isolates are created.

    1. Call initialize_platform(platform) with a SharedRef<Platform>.
    2. Call initialize().

    Note that dispose() is permanent and cannot be undone. dispose_platform() should only be called after V8 has been disposed.

  6. Implement a custom PlatformImpl for event loop integration

    main

    To integrate V8 foreground tasks into your own event loop (e.g., tokio), implement the PlatformImpl trait and use new_custom_platform.

    When using a custom platform, V8 transfers ownership of tasks to your implementation via the following methods. You are responsible for scheduling these tasks and calling Task::run() or IdleTask::run(deadline) on the isolate's foreground thread.

    Methods to override:

    • post_task: Called for TaskRunner::PostTask.
    • post_non_nestable_task: Called for TaskRunner::PostNonNestableTask.
    • post_delayed_task: Called for TaskRunner::PostDelayedTask.
    • post_non_nestable_delayed_task: Called for TaskRunner::PostNonNestableDelayedTask.
    • post_idle_task: Called for TaskRunner::PostIdleTask.

    Implementations must be Send + Sync.

  7. Initialize and shutdown the C++ Garbage Collector process

    main

    Before creating a Heap, you must initialize the process-global garbage collector. This can be called multiple times if paired with shutdown_process().

    Note: shutdown_process() is unsafe and should only be called after destroying the last used heap. Some process-global metadata may not be reused upon a subsequent initialize_process() call.

  8. Troubleshoot GN argument errors

    main

    If you encounter the error unknown argument: '-gno-inline-line-tables' during the build, set the following environment variable to disable that flag:

    export GN_ARGS="no_inline_line_tables=false"
  9. Troubleshoot V8 initialization crashes on non-main threads

    main

    If your program crashes when initializing V8 on a non-main thread, it may be due to the CPU's PKU feature being enabled.

    Workaround: Use v8::new_unprotected_default_platform to resolve this issue.

  10. Configure V8 build environment variables

    main

    When building from source, you can control the V8 build process using several environment variables:

    VariableDescription
    V8_FORCE_DEBUGSet to true to link against a debug build of V8 instead of the default release build.
    RUSTY_V8_MIRRORSpecifies the URL or file path to fetch binary builds from. Default is https://github.com/denoland/rusty_v8/releases.
    RUSTY_V8_ARCHIVESpecifies a specific V8 library URL or path to use.
    PYTHONPath to the Python binary to use for the build.
    GNPath to the gn binary to skip downloading.
    NINJAPath to the ninja binary to skip downloading.
    CLANG_BASE_PATHPath to the directory containing an llvm/clang installation to skip downloading clang.
    GN_ARGSArguments passed directly to gn.
    SCCACHE / CCACHEPath to sccache or ccache to speed up compilation.
    GN_ARGS='extra_cflags=["-DV8_TLS_USED_IN_LIBRARY"]'Use this for Linux targets to ensure the static archive is compatible with downstream cdylib/shared-library targets.
  11. Run a basic JavaScript execution example

    main

    To use rusty_v8, you must first initialize the V8 platform and the V8 engine itself. Then, you create an Isolate, manage memory using HandleScope and ContextScope, and finally compile and run a Script.

    Note that HandleScope and ContextScope are typically used with std::pin::pin! to ensure they are not moved in memory while they manage V8 handles.

    let platform = v8::new_default_platform(0, false).make_shared();
    v8::V8::initialize_platform(platform);
    v8::V8::initialize();
    
    let isolate = &mut v8::Isolate::new(Default::default());
    
    let scope = std::pin::pin!(v8::HandleScope::new(isolate));
    let scope = &mut scope.init();
    let context = v8::Context::new(scope, Default::default());
    let scope = &mut v8::ContextScope::new(scope, context);
    
    let code = v8::String::new(scope, "'Hello' + ' World!'").unwrap();
    println!("javascript code: {}", code.to_rust_string_lossy(scope));
    
    let script = v8::Script::compile(scope, code, None).unwrap();
    let result = script.run(scope).unwrap();
    let result = result.to_string(scope).unwrap();
    println!("result: {}", result.to_rust_string_lossy(scope));
  12. Manage low-level embedder data slots in an Isolate

    main

    For manual memory management or interfacing with C-style APIs, you can use raw data slots. The number of available slots is determined by the underlying V8 engine.

    Methods:

    • get_number_of_data_slots(&self) -> u32: Returns the maximum number of available slots (range 0 <= n < Isolate::get_number_of_data_slots()).
    • set_data(&mut self, slot: u32, data: *mut c_void): Associates a raw pointer with a specific slot index.
    • get_data(&self, slot: u32) -> *mut c_void: Retrieves the raw pointer from a specific slot. Returns NULL if the slot has never been set.