2.9 V8

Overview

V8 stands as the cornerstone of Deno, holding paramount importance within its architecture. In the realm of Deno's vital libraries, tokio also plays a pivotal role, but it is V8 that serves as the execution grounds for JavaScript code. Without the steadfast presence of V8, Deno's functionality would come to a grinding halt.

Originating from the laboratories of Google, V8 is a high-performance JavaScript and WebAssembly engine, bestowed with the gift of being open-source. It emerges from the depths of code, sculpted in the elegant language of C++. Its prowess extends beyond Deno, permeating the digital landscapes of Chrome and Node.js. A newcomer to Deno, it embarks on yet another voyage to empower the realm of this innovative runtime.

V8 serves as the nurturing soil where ECMAScript and WebAssembly find their roots and flourish. Its versatility is far-reaching, with compatibility spanning across various operating systems – Windows 7 and beyond, macOS 10.12 and higher, and various Linux systems sporting x64, IA-32, ARM, or MIPS processors. This powerful engine possesses the autonomy to manifest itself as a standalone entity or merge seamlessly with any C++ application, enhancing its capabilities manifold.

The role undertaken by V8 encompasses an array of critical functions. It commences its symphony by orchestrating the compilation and execution of JavaScript source code, breathing life into the virtual world it shapes. Beyond this, V8 takes up the mantle of stewarding memory allocation for a diverse array of objects, even shouldering the responsibility of gracefully ushering out objects that have overstayed their welcome. This act of digital tidying, often referred to as garbage collection, stands as V8's pièce de résistance, setting it apart from the cacophony of competing technologies. The precision of V8's garbage collector is, without a shadow of doubt, a pivotal factor in the symphony of its remarkable performance.

A key attribute bestowed upon V8 is its prowess to bridge the chasm between C++ and JavaScript realms. With remarkable finesse, V8 enables any C++ application to extend a warm invitation to its own objects and functions, beckoning JavaScript code to interact and collaborate harmoniously. This realm of interaction remains under the sovereign rule of the application, allowing it the freedom to curate the objects and functions it deems fit to expose to the JavaScript domain.

To delve into the intricate tapestry of V8's capabilities, one can embark on a journey through its expansive public API. A treasure trove of insights and knowledge awaits the curious minds at https://v8.dev/, beckoning them to explore the depths and heights that this remarkable engine has to offer.

Key concepts

V8, the heart of Deno's runtime, boasts an extensive size, comprising approximately one million lines of code. Within V8 lies a substantial public API, offering a multitude of functionalities and capabilities. Navigating through this intricate JavaScript engine in its entirety can be a daunting task, and thus, we will focus on exploring several pivotal concepts to provide a foundational understanding.

To illustrate the essence of V8's significance, let's take into account a simple "hello world" program. (ref: https://chromium.googlesource.com/v8/v8/+/refs/tags/11.8.34/samples/hello-world.cc)

// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/libplatform/libplatform.h"
#include "include/v8-context.h"
#include "include/v8-initialization.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-primitive.h"
#include "include/v8-script.h"
int main(int argc, char* argv[]) {
  // Initialize V8.
  v8::V8::InitializeICUDefaultLocation(argv[0]);
  v8::V8::InitializeExternalStartupData(argv[0]);
  std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
  v8::V8::InitializePlatform(platform.get());
  v8::V8::Initialize();
  // Create a new Isolate and make it the current one.
  v8::Isolate::CreateParams create_params;
  create_params.array_buffer_allocator =
      v8::ArrayBuffer::Allocator::NewDefaultAllocator();
  v8::Isolate* isolate = v8::Isolate::New(create_params);
  {
    v8::Isolate::Scope isolate_scope(isolate);
    // Create a stack-allocated handle scope.
    v8::HandleScope handle_scope(isolate);
    // Create a new context.
    v8::Local<v8::Context> context = v8::Context::New(isolate);
    // Enter the context for compiling and running the hello world script.
    v8::Context::Scope context_scope(context);
    {
      // Create a string containing the JavaScript source code.
      v8::Local<v8::String> source =
          v8::String::NewFromUtf8Literal(isolate, "'Hello' + ', World!'");
      // Compile the source code.
      v8::Local<v8::Script> script =
          v8::Script::Compile(context, source).ToLocalChecked();
      // Run the script to get the result.
      v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
      // Convert the result to an UTF8 string and print it.
      v8::String::Utf8Value utf8(isolate, result);
      printf("%s\n", *utf8);
    }
    {
      // Use the JavaScript API to generate a WebAssembly module.
      //
      // |bytes| contains the binary format for the following module:
      //
      //     (func (export "add") (param i32 i32) (result i32)
      //       get_local 0
      //       get_local 1
      //       i32.add)
      //
      const char csource[] = R"(
        let bytes = new Uint8Array([
          0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01,
          0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07,
          0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01,
          0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b
        ]);
        let module = new WebAssembly.Module(bytes);
        let instance = new WebAssembly.Instance(module);
        instance.exports.add(3, 4);
      )";
      // Create a string containing the JavaScript source code.
      v8::Local<v8::String> source =
          v8::String::NewFromUtf8Literal(isolate, csource);
      // Compile the source code.
      v8::Local<v8::Script> script =
          v8::Script::Compile(context, source).ToLocalChecked();
      // Run the script to get the result.
      v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
      // Convert the result to a uint32 and print it.
      uint32_t number = result->Uint32Value(context).ToChecked();
      printf("3 + 4 = %u\n", number);
    }
  }
  // Dispose the isolate and tear down V8.
  isolate->Dispose();
  v8::V8::Dispose();
  v8::V8::DisposePlatform();
  delete create_params.array_buffer_allocator;
  return 0;
}

This uncomplicated program's purpose is to display the phrase "hello world." Even though it might seem basic, the hello world program provides us with a glimpse into several important V8 concepts. Let's take a step-by-step journey to explore each of these concepts.

In this straightforward piece of code, its sole function is to output the famous greeting "hello world." Despite its apparent simplicity, the hello world program serves as a portal into a range of essential V8 concepts. Let's proceed to examine these concepts individually, delving into each one step by step.

Isolate

An "isolate" refers to a distinct and separate occurrence of the V8 engine, which powers the execution of JavaScript code. Each isolate operates in its own separate environment, with its unique set of data and variables. It's important to note that any objects or elements within one isolate should not be used within another isolate.

When V8 is set in motion, it automatically generates a default isolate and sets it as the current working context. Think of this as the starting point for V8's activities. However, the programmer, known as the embedder, has the ability to establish additional isolates. These isolates can function simultaneously, enabling the execution of code on multiple threads in parallel. It's important to remember that only one thread can actively interact with an isolate at any given moment.

Picture isolates as self-contained virtual machines (VMs), each having its own dedicated storage area known as a heap. This heap is where data is stored during the program's runtime. Initializing an isolate is always the initial and fundamental step in this process, setting the stage for all subsequent actions. By carefully managing isolates, developers can create efficient and organized environments for running JavaScript code.

Context

Within the realm of v8, the term "context" refers to a distinct execution environment that permits separate and unrelated JavaScript applications to operate within a single instance of v8. To set a specific context for running JS code, a user program must provide this information. Once a context is established, you have the flexibility to enter and exit it as many times as necessary.

Imagine you're in context A, executing your JavaScript operations. However, the beauty lies in the fact that you can also transition into an entirely different context, let's call it B. This transition involves replacing context A with context B as the currently active environment. And when you eventually exit context B, context A is reinstated as the ongoing operational context.

The rationale behind introducing the concept of contexts in V8 stems from the need to cultivate separate, fresh JavaScript environments for various elements such as windows and iframes within a web browser. This way, each element can carry out its JavaScript activities without interfering with the others. The utility of contexts in V8 surfaces when considering scenarios where isolation of JavaScript execution is essential to ensure smooth and independent functioning of different components within a broader application or system.

Compile and run

The following stages in the process involve the compilation and execution of the JavaScript (JS) code. Now, you might be wondering, why is there a need for compilation? After all, isn't JS typically interpreted? While it's true that JS is indeed interpreted, the V8 engine takes things a step further by incorporating a certain level of compilation.

But why the extra compilation, you ask? Well, let's break it down. JS code is initially interpreted, but the V8 engine enhances its performance by engaging in a form of compilation. This compilation phase is designed to ramp up the speed at which the code can be executed.

The magic behind the speed of the V8 engine lies in its utilization of a technique called Just in Time (JIT) compilation. Here's how it works: the JS code is first compiled into native machine code as part of the JIT process. When your code runs, this machine code is carefully examined and even re-compiled dynamically to ensure optimal performance.

This dynamic approach to compilation and execution results in a significant boost in speed. It's like having a finely-tuned engine in your car that constantly adjusts itself while you're driving to deliver the best possible performance. In the case of the V8 engine, this continuous analysis and optimization of machine code contribute to the swift execution of your JS programs.

--

This section covered the basics of v8, but delving into the intricacies of V8's complexity requires more than a brief overview. Indeed, the subject of V8 is so multifaceted and intricate that it warrants an entire book dedicated solely to its exploration.

Last updated