V8 Engine | V8 Engine Javascript

Learn via video courses
Topics Covered

Overview

Chrome V8 or just V8 is an open-source Javascript and WebAssembly engine developed by Google, written in C++. The V8 engine was initially built for the Chromium-based browsers and Chrome browsers to improve the performance of javascript execution, but the latest versions can execute javascript code either within or outside the browser, which enables server-side scripting. Nowadays, the V8 engine is the base for various technologies like Node.js, MongoDB, and desktop apps like Electron.

Introduction

Chrome V8 is a powerful and high-performance engine that translates javascript code into machine code so that computers can understand it, and then executes the translated or compiled code.

v8 logo

A javascript engine is a program that takes your javascript code as input and generates machine executable code or bytecode (bytecode is an abstraction of machine code).

V8 engine is completely independent of the browser in which it runs or is hosted, which is why it powers an incredible amount of server-side code written in javascript. The V8 engine is based on the JIT i.e. Just in Time compilation pipeline.

Why was the V8 Engine Created?

Many traditional languages like java and C# compile the code and create bytecode and this code is finally executed by machines. But generating bytecode is an additional overhead that reduces the efficiency of the compiler. This is the reason why the V8 engine was created that has enabled direct compile the javascript code into machine language i.e. a language that can be executed directly by the system instead of using an interpreter. In order to obtain speed, the engine also optimizes code dynamically at runtime based on code execution profile heuristics.

Besides this, the V8 engine uses other optimization techniques like inline expansion, copying elision, and inline caching, which makes V8 a super fast javascript engine.

But Is V8 the Best Engine Available Today?

For this, we need to understand that a javascript engine is a program written in some language that needs to do a certain task as efficiently as possible. Now, this efficiency can be compared either in terms of speed or in terms of memory usage.

For example, if you are building your own web browser it means you have plenty of memory, then V8 would be a great choice. But let's say you are making a small wearable IoT (internet of things) device then you will have very little memory available. In that case, you may use Duktape or Jerryscript, which may be slower but fit better in memory.

How does V8 JavaScript Engine Work?

A javascript engine is a program that takes your javascript code as input and generates machine executable code or bytecode. Anyone can write a JS engine as long as they follow the standard set by the ECMAScript standards.

The V8 engine is written in C++ and has the following threads running internally:

  • There is a main thread that loads, compiles, and runs the JS code
  • Another thread is used for optimization and compilation, so the main thread continues to execute while the first thread optimizes the running code.
  • The third thread is only used for feedback, telling the runtime which methods need further optimization.
  • Few other threads for handling garbage collection.

v8 engine steps

Preparing the Source Code

Technically, it's not part of the V8's job. It is the renderer process of the browser that initializes the following two items:

  • Host environment
  • V8 engine

Every browser has several rendering processes. Typically, each browser tab has a render process and initializes a V8 instance.

process of preparing source code

In our context, the host environment is the browser. Therefore, we will use the “browser” and the “host environment” interchangeably in this article.

V8 engine first downloads the source code via a network, cache, or service worker. Once the code is downloaded, it is changed in a way that the compiler can understand. This process of changing the downloaded code so that compiler can understand it is called parsing and it consists of two parts: the scanner and the parser.

The scanner takes the javascript file and converts it to a list of known tokens. There is a list of all JS tokens in the keywords.txt file.

The parser encounters a script tag with a source. The source code inside this script is read as a UTF-16 byte stream into the byte stream decoder. This byte stream decoder then decodes the bytes into a token that is sent to the parser. To increase efficiency, the engine tries to avoid analyzing code that is not necessary right away.

Why Do We Need a Host Environment?

A host environment provides everything that a JavaScript engine depends on:

  • Call stack
  • Heap
  • Callback queue
  • Event loop
  • Web API and Web DOM

When a user interacts with a web page, it triggers a series of events that are added to a callback queue along with the associated callback function. These are handled by an infinite while loop, called the event loop, which keeps on fetching a callback from the queue and compiles and executes the javascript in the callback.

create host environment working

But why does the host environment stores the data in two different places?

  1. Trading space for speed: The call stack requires continuous space in memory to make the process faster but contiguous space in memory is rare. In order to solve this problem, browser developers limit the maximum size and this is the reason for the stack-overflow error. Browsers typically store a limited amount of data in the call stack, such as integers and other primary data types.
  2. Trading speed for space: The heap does not require contiguous space to store large data such as objects. The trade-off is that the heap is relatively slow to process data.

Compiling Code and Just-in-Time (JIT) Paradigm

At this step, the V8 engine creates nodes based on the token it receives and converts them into Abstract Syntax Tree (AST), and generates scopes.

V8 engine can't understand javascript language so it must be structured before processing. AST has a tree structure where each node denotes a construct occurring in the code which is easy for the V8 engine to digest.

compiler working

How an Abstract Syntax Tree (AST) looks like?

For this, let's look at a simple example:

Similarly, each line of code will be converted into an AST.

ast tree

Now, the most common way to convert code is to do precompilation i.e. the code is converted to machine code in the compilation stage before the execution of the program.

This approach is used by programming languages such as C++, Java, etc.

Whereas, dynamically typed languages like JavaScript and Python use the interpretation approach where each line of code is executed at runtime as it is impossible to determine the exact type before execution.

Precompilation allows all code to be evaluated together, resulting in better optimization and ultimately better-performing code. Interpreted implementations, on the other hand, are easier, but usually slower than a precompiled option.

So to work with dynamic languages more effectively, a new approach called Just-in-Time (JIT) compilation was developed which combines the best interpretation and precompilation techniques.

Interpreter

V8 uses an interpreter called Ignition which walks through the AST and generates byte code. An interpreter executes each line of bytecode from top to bottom. Once the bytecode is generated, the abstract syntax tree is deleted to free up memory space.

interpreter working

Now let's take an example and generate the bytecode for it manually:

V8 interpreter or ignition has a place where you can store and read values known as an accumulator. Accumulator eliminates the need to push and pop the top of the stack. It is also an implicit argument for many bytecodes and usually holds the result of the operation.

Execution of Machine Codes

In this step, ignition uses a table of controllers keyed by the bytecode to interpret instructions. For each bytecode, ignition can find the corresponding handler functions and execute them with the provided arguments.

As you run bytecode, V8 looks for opportunities to optimize your code.

When frequently used bytecodes are detected, V8 marks them as "hot". The hot code is then translated into efficient machine code and consumed by the Central Processing Unit (CPU).

execution process

What if optimization fails? The compiler breaks down the code and allows the interpreter to execute the original bytecode.

However, the V8 team introduces the bytecodes as the engine evolves. Why? Because using machine codes brings some difficulties.

1. Machine codes require a lot of memory

The V8 engine stored the compiled machine code in memory and reused it on page load.

Compiling machine codes can turn 10,000 JavaScripts into 20 million machine codes. This is about 2000 times more memory space.

difference in memory consumption

Even though bytecode is larger than the original JavaScript but much smaller than the corresponding machine code.

Due to the reduced size, browsers can cache all the compiled bytecodes, skip all the previous steps and execute them directly.

2. Machine Code Is Not Always Faster than Bytecode

Bytecode takes less time to compile, but the trade-off is slower execution steps. The interpreter must interpret the bytecode before execution.

machine code is faster

So which one is faster? It depends. The developer needs to balance the two options while creating a powerful bytecode interpreter and a smart optimizing compiler.

V8 interpreter, Ignition, is the fastest on the market.

The V8 optimization compiler is the famous turbofan that compiles highly optimized machine code from the bytecode.

3. Machine Codes Increase the Complexity of Development

Different CPUs can have different architectures. Each of them can understand only one type of machine language. There are many processor architecture designs in the market like ARM64, X64, S397, etc.

If the browser only used machine language, it would have to handle many things separately, therefore, an abstract (bytecode) is required.

Compiler

The compiler works ahead of time and creates a translation of the written code and compiles it into a low-level machine-readable language.

The V8 engine uses a compiler known as Turbofan which takes bytecode from Ignition, inputs function feedback, applies a set of reductions based on it, and generates machine code.

But this doesn't guarantee that things won't change in the future.

For example, Turbofan optimized the code based on the assumption that some additions always add integers.

But what happens when you get a string? This process is called deoptimization. In such a case it returns to interpreted code, resumes execution, and updates the type of feedback.

compiler working deoptimization

What is Sandboxing?

Sandboxing refers to a method in which you use an isolated environment or "sandbox" for testing.

A sandbox is an environment for running software that is isolated and separated from other environments, even those on the same computer. In other words, a sandbox provides an isolated and safe environment to install and run a program, especially a suspect one without exposing the rest of your system and keeping your system protected from any zero-day threats of processes running in the sandbox environment.

In Chrome V8, each process is sandboxed, which ensures that JavaScript functions run separately on it, and the execution of one piece of code does not affect any other piece of code and at the same time makes sure that this does not slow down performance which makes sandboxing a key feature of Chrome V8.

Example of V8 JavaScript Engine

Many developers use the V8 engine to build and deploy serverless scripting products for their websites.

V8 engine comes with WebAssembly support, which allows support for many programming languages.

Cold starts are a huge problem in serverless computing, but running a function on V8 means that your function can be "spun up" and run within few milliseconds.

Below is an example of a script deployed in the Serverless Script Engine Sandbox.

This script allows developers to identify the server from which the request was sent. Developers can use this information to provide region-specific services in real-time.

Other JavaScript Engines

  1. Chakra: It is a JavaScript engine developed by Microsoft which is to power the Internet Explorer web browser. A distinctive feature of this engine is the JIT compilation of scripts on a separate CPU core in parallel with the web browser.
  2. SpiderMonkey: It is a JavaScript and WebAssembly implementation engine developed by Brendan Eich at Netscape Communications, currently used for Mozilla Firefox web browser, Servo, and various other projects. It is written in C++, Rust, and JavaScript.
  3. Webkit: It is a browser engine developed by Apple and is primarily used by Safari, Mail, App Store, and many other apps on macOS, iOS, and Linux. WebKit was also used by BlackBerry Browser, PlayStation consoles from PS3 onwards, and browsers in Amazon Kindle e-readers.
  4. Nashorn: It is a JavaScript engine written in the Java programming language, first developed by Oracle and then by the OpenJDK community. Nashorn is included in Java 8 through JDK 14.

Conclusion

  • V8 is an open-source Javascript and WebAssembly engine developed by Google, written in C++.
  • V8 uses an interpreter called Ignition which walks through the AST and generates byte code. An interpreter executes each line of bytecode from top to bottom.
  • Abstract Syntax Tree (AST) is a way to represent the syntax of a programming language as a hierarchical, tree-like structure.
  • V8 engine uses a compiler known as Turbofan which takes bytecode from Ignition, inputs function feedback, applies a set of reductions based on it, and generates machine code.
  • Sandboxing refers to a method in which you use an isolated environment or "sandbox" for testing.