JavaScript Internals — Nuts & Bolts of the language (Part 1)

A comprehensive guide to understand the internals of the JavaScript language

Akmal Ahmed
5 min readFeb 13, 2021
Photo by Eduardo Buscariolli on Unsplash

Introduction

JavaScript is a beginner-friendly language. The language was designed and developed by Brendan Eich when he was working for Netscape back in 1995. Brendan Eich took just 10 days to implement this awesome language. The main purpose of the language was to serve the Netscape browser. JS did not restrict itself within browser environments. The language has been used in servers, mobile, and desktop application developments as well. The recent launch of SpaceX Dragon took JavaScript into space (Click here to read the full story).

You can check the video from codedamn youtube channel below.

Most JavaScript developers don’t know the internal architecture of the language. Therefore this and upcoming articles will cover the internal concepts and technologies of JavaScript language such as JS Engine, Garbage collection, Node.JS, Callstacks & Event loops, etc in depth. This article mainly focuses on the internal architecture of Google’s open-source V8 JavaScript engine in detail.

JavaScript Engine

Computers cannot run JavaScript code (which has .js extension) directly. Because JavaScript falls into the category of high-level languages. Therefore the language needs to be transformed into a format where computers could easily understand. Gladly the translation role has been carried out by JavaScript engines. Some of the JS engines are listed below.

Google V8 Engine Logo from wikipedia
  • V8: Google’s open-source JS engine, specifically developed for the Chrome browsers. Node.js uses V8 as its runtime.
  • SpiderMonkey: This is known as the first JavaScript engine, which is written by the founder of JavaScript Brendan Eich. This engine is used by Mozilla browsers.
  • Chakra: This is developed by Microsoft. Used in Microsoft Edge browsers
  • Hermes: Developed by Facebook to optimize JavaScript (React Native) code for mobile environments.
Role of JavaScript Engine

The aforementioned JavaScript engines are mainly written in C/C++ languages.

V8 Engine Architecture

V8 Engine Architecture

The above image illustrates the internals of the V8 engine. There are 3 important steps the V8 engine takes during the execution of JavaScript.

1. Converting the JavaScript (*.js) code into Abstract Syntax Tree (AST)

This is the initial phase of JS execution. JavaScript code is nothing but a String. In this phase, JS code strings will be parsed as a UTF-16 byte stream into a byte stream decoder. This byte-stream decoder will generate decoded tokens.

UTF-16 Decoder decodes the byte stream

Then these parsed tokens will be sent to a component called Scanner. The scanner will break the code into separate tokens such as keywords & identifiers etc.

Parsing tokens into AST (Abstract Syntax Tree)

The scanner will parse these extracted tokens into Preparser before parsing them into the Parser. Parser’s main job is to check valid JavaScript syntaxes and groupings of code. Preparser lazy parses already parsed code to improve the efficiency.

2. Converting the AST to byte-code

JavaScript follows the Just In Time (JIT) compilation approach. Which combines the best parts of the Compilers and Interpreters. Interpreters follow the REPL (Read-Eval-Print-Loop) approach. Each time during the execution interpreter evaluates the code, which causes slowness in execution. When the Interpreter runs 1000 times, It’ll run the code 1000 times without any optimization.

On the other hand, compilers compile the entire source code into machine-executable code. Compilers can optimize the code based on hot spots, which makes the compilers much faster than the interpreters.

The main reason for adopting the JIT compilation approach is to avoid the re-translation, which makes the code more optimized and faster.

Compiler (Turbofan) & Interpreter (Ignition) of V8 engine

3. Byte-code to machine code conversion

The V8 engine has a compiler named TurboFan and an interpreter named Ignition. Ignition interpreter converts the generated AST into bytecode. The Ignition interpreter walks through the AST and creates unoptimized bytecode. After creating the bytecode, generated AST will be deleted to save the memory. When the bytecode runs, information about the running code will be collected. During consistent execution, the extracted profiler data will be parsed into the TurboFan compiler, which generates optimized machine code.

Next time when the bytecode is executed, TurboFan will replace the bytecode with the optimized machine code. If a running bytecode is determined as a non — hot spot region then the TurboFan compiler will deoptimize the code into bytecode.

You can check the generated bytecode, via the print-bytecode flag as shown below.

node --print-bytecode index.js # Will print the generated bytecode
Sample JavaScript code (index.js)
Generated bytecode from the V8 engine after running with print-bytecode flag

Wrapping Up

So far we’ve discussed the internal architecture of the V8 engine. You can learn some additional information about the discussed concepts from the links below.

https://ponyfoo.com/articles/an-introduction-to-speculative-optimization-in-v8

I’ve skipped some descriptive pieces of information in this article, such as Garbage collection and Webassembly compilation, etc. In the upcoming articles, we’ll discuss more of these topics and some advanced concepts in JavaScript.

--

--