deno_core

repository·main·Indexed 19 days ago

https://github.com/denoland/deno_core

The foundational Rust crate for the Deno runtime, providing V8 integration, the JavaScript snapshot system, and the JsRuntime abstraction for executing JavaScript within a Rust environment. It includes the op2 system for defining high-performance Rust operations, the extension! macro for registering Rust functions and native JS classes via CppGC, and support for async ops and fastcalls.

Tokens
10.8K
Snippets
19
Records
33
Agent score
66%

What's inside deno_core

  1. Optimize async ops with lazy or deferred polling

    main

    You can control how the runtime polls asynchronous ops using specific markers:

    • async(lazy): Defers polling until a later time. This can increase throughput and make the initial submission faster, but increases latency for ops that would have been ready immediately. Use this only after careful benchmarking.
    • async(deferred): Polls the op immediately, but defers the resolution of ready results until a later run of the event loop. This is a specialized pattern and should generally be avoided unless you have a specific requirement.

    Note: Lazy and deferred calls may be implemented as fastcalls, though resolution still occurs on the slow path.

  2. Execute JavaScript with JsRuntime

    main

    The JsRuntime is the primary abstraction in deno_core for executing JavaScript. It provides the V8 bindings necessary to run JS code within a Rust environment.

    Important: Driving the Event Loop JsRuntime implements an event loop abstraction that tracks pending tasks such as asynchronous operations and dynamic module loads. It is the user's responsibility to drive this loop by calling the JsRuntime::run_event_loop method. This method must be executed within the context of a Rust future executor, such as tokio or smol.

    // Note: Actual implementation requires setting up JsRuntime and a Rust executor
    // like tokio or smol to call:
    js_runtime.run_event_loop();
  3. Use async ops in op2

    main

    Asynchronous calls are automatically inferred from the function signature. Deno eagerly polls the op; if it is immediately ready, it returns the value. If not, it returns None and handles the future via Deno's pending op system.

    Supported signatures:

    • async fn op_xyz(/* ... */) -> X {}
    • fn op_xyz(/* ... */) -> impl Future<Output = X> {}

    These are desugared to a function that accepts a hidden promise_id: i32 and returns Option<X>.

    // Example of an async op signature
    async fn op_xyz() -> X {}
    
    // Or using impl Future
    fn op_xyz() -> impl Future<Output = X> {}
  4. Manage Resources and Streams

    main

    Deno uses Resources to represent system-like streams. A Resource is a Rust struct implementing the Resource trait, which manages its own lifetime via a unique integer handle.

    Workflow:

    1. An op instantiates a Resource and returns its integer handle to JavaScript.
    2. JavaScript uses the handle to interact with the resource.
    3. The resource is explicitly closed using the Deno.core API.

    Key APIs:

    • Deno.core.close(id): Closes the resource with the given integer ID.
    • Deno.core.tryClose(id): Attempts to close the resource.
    • Deno.core.read* and Deno.core.write*: APIs used to perform I/O on resources that implement read/write traits.
  5. How JsRuntime works

    main

    The JsRuntime is the core of Deno, acting as a wrapper around V8's Isolate and Context. To use it, an embedder instantiates a JsRuntime with a set of extensions and then polls the event loop to drive execution.

    An extension provides:

    • ECMAScript modules: JavaScript or TypeScript code loaded at runtime.
    • ops: Fast JavaScript-to-Rust function calls that can be synchronous or asynchronous, and fallible or infallible.

    To maintain state, the runtime uses:

    • OpState: A general-purpose Rust-side storage.
    • Resources: Objects readable/writable by both script and Rust.
    • cppgc objects: Objects held on the JavaScript side that can be accessed by Rust but not directly by JS.
  6. Key differences in snapshot creation and extensions

    main

    When implementing a custom JavaScript runtime using deno_core, note the following modern API patterns compared to older tutorials:

    • Snapshot API: The create_snapshot() API has evolved; ensure you are using the current signature provided by the latest deno_core version.
    • Extension Registration: Use the extension!(...) macro instead of the deprecated Extension::builder() pattern.
    • Ops API: Use the #[op2] attribute for defining operations.
    • Module System: Extensions can now be ESM-based.

    Note that this specific example omits TsModuleLoader implementation to maintain conciseness.

  7. Understand the Event Loop and Timers

    main

    Forward progress in the runtime (module resolution, promise resolution, timer resolution, etc.) occurs through event loop callbacks.

    In JsRuntime, the method do_js_event_loop_tick_realm polls these sources and routes them to V8. To prevent unexpected callbacks into the engine, results are only forwarded to V8 during the event dispatch phase.

    Timers: deno_core provides high-level timer support via:

    • queueUserTimer
    • refTimer
    • unrefTimer

    These are sufficient to implement both Web-compatible and Node.js-compatible timer behaviors.

  8. Optimize startup with Snapshot Management

    main
    To optimize JsRuntime startup times, you can use JsRuntimeForSnapshot. This allows you to create a runtime that is serialized into a V8 snapshot. This snapshot data can then be passed to a standard JsRuntime during boot, allowing it to pre-initialize the V8 isolate and context from the previously captured state.
  9. Generate optimized V8 functions with `op2`

    main

    The deno_ops module provides a proc_macro called op2 used to generate highly optimized V8 functions directly from Rust functions. This allows for efficient communication between the Rust backend and the JavaScript runtime. You can use the #[op2(fast)] attribute to declare an operation.

    To use it, you must import op2 and extension from deno_core.

    use deno_core::{op2, extension};
    
    // Declare an op.
    #[op2(fast)]
    pub fn op_add(a: i32, b: i32) -> i32 {
      a + b
    }
    
    // Register with an extension.
    extension!(
      math,
      ops = [op_add]
    )
  10. Implement CppGC object inheritance

    main

    CppGC objects support prototype-based inheritance mirroring JavaScript's class Child extends Parent.

    Requirements

    • All types in the chain must use #[repr(C)].
    • The base type must be the first field of the derived struct (offset 0).
    • Leaf types: Use #[derive(CppgcInherits)].
    • Root base types: Use #[derive(CppgcBase)] and #[op2(base)] on the impl block.
    • Intermediate types: Use both #[derive(CppgcInherits, CppgcBase)] and #[op2(base, inherit = ParentType)].
    • Registration: Register all types in the extension's objects list, with base types listed before derived types.

    Example: Base and Derived Classes

    // Base Class
    #[derive(CppgcBase)]
    #[repr(C)]
    pub struct Shape {
      sides: GcCell<u32>,
    }
    
    #[op2(base)]
    impl Shape {
      #[constructor]
      #[cppgc]
      fn new(sides: u32) -> Shape { ... }
    }
    
    // Derived Class
    #[derive(CppgcInherits)]
    #[cppgc_inherits_from(Shape)]
    #[repr(C)]
    pub struct Rectangle {
      base: Shape, // Must be first
      width: GcCell<f64>,
      height: GcCell<f64>,
    }
    
    #[op2(inherit = Shape)]
    impl Rectangle {
      #[constructor]
      #[cppgc]
      fn new(width: f64, height: f64) -> Rectangle { ... }
    }
    // Base Class
    #[derive(CppgcBase)]
    #[repr(C)]
    pub struct Shape {
      sides: GcCell<u32>,
    }
    
    #[op2(base)]
    impl Shape {
      #[constructor]
      #[cppgc]
      fn new(sides: u32) -> Shape {
        Shape { sides: GcCell::new(sides) }
      }
    
      #[getter]
      fn sides(&self, isolate: &v8::Isolate) -> u32 {
        *self.sides.get(isolate)
      }
    }
    
    // Derived Class
    #[derive(CppgcInherits)]
    #[cppgc_inherits_from(Shape)]
    #[repr(C)]
    pub struct Rectangle {
      base: Shape, // Must be first
      width: GcCell<f64>,
      height: GcCell<f64>,
    }
    
    #[op2(inherit = Shape)]
    impl Rectangle {
      #[constructor]
      #[cppgc]
      fn new(width: f64, height: f64) -> Rectangle {
        Rectangle {
          base: Shape { sides: GcCell::new(4) },
          width: GcCell::new(width),
          height: GcCell::new(height),
        }
      }
    
      #[fast]
      fn area(&self, isolate: &v8::Isolate) -> f64 {
        *self.width.get(isolate) * *self.height.get(isolate)
      }
    }
  11. Encode and decode values between Rust and V8 with serde_v8

    main

    The serde_v8 crate provides an efficient encoding layer to bijectively map between Rust and V8/JavaScript values. It is designed to be a high-performance alternative to serde_json for the Deno op-layer, handling all non-buffer values.

    The API is modeled after serde_json and provides two primary functions:

    • to_v8: Maps Rust values to V8 values (similar to serde_json::to_string).
    • from_v8: Maps V8 values to Rust values (similar to serde_json::from_str).