0% found this document useful (0 votes)
90 views

JS Works - Draft

The document discusses the internal workings of the V8 JavaScript engine, including how it uses just-in-time compilation, hidden classes, and inline caching to optimize code execution. It describes the compilation process from abstract syntax tree to machine code and techniques like inlining, hidden classes, inline caching, and garbage collection used in V8.

Uploaded by

niteeshyadav
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
90 views

JS Works - Draft

The document discusses the internal workings of the V8 JavaScript engine, including how it uses just-in-time compilation, hidden classes, and inline caching to optimize code execution. It describes the compilation process from abstract syntax tree to machine code and techniques like inlining, hidden classes, inline caching, and garbage collection used in V8.

Uploaded by

niteeshyadav
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 11

JavaScript working: inside the V8

engine
The first post of the series focused on providing an overview of
the engine, the runtime and the call stack. This second post will
be diving into the internal parts of Google’s V8 JavaScript
engine.

Overview
Before diving into JS engine, lets discuss basic working of any
programming language. The computer is made of
microprocessors and we write code to instruct them. But main
question is how we make microprocessors understand the code
that we are writing? They don’t understand high level languages
like Java, C#, Python etc., but only understand machine code.

For this part, compilers and interpreters comes into picture.


There are two ways in which we could translate the JavaScript
code to machine code. In case of code compilation, machine has
a better understanding of what is going to happen next before
code starts to run. In this way we can makes the execution faster
later, however it will take some time initially.

On the other hand, when the code is interpreted, the execution


process starts immediately. Due to this reason it is faster, but
only in case of small applications. Since it lacks optimizations, it
is slow with large applications.

To overcome these challenges, ECMAScript engines comes into


picture. They make best use of both the worlds and created
JIT(Just-in-time) compiler. JavaScript is both compiled as well
as interpreted. However actual implementation and order
depends on the engine.

Few of the JS engines are mentioned below -

 V8 is Google’s implementation of the engine for its Chrome


browser.
 SpiderMonkey was the first engine built for Netscape
Navigator and now powers FireFox.
 JavaScriptCore is what Apple uses for the Safari browser.
We can also create our own engine using ECMAScript standard.

A JavaScript engine is a program or an interpreter which


executes JavaScript code. A JavaScript engine can be
implemented as a standard interpreter, or just-in-time compiler
that compiles JavaScript to bytecode in some form.

This is a list of popular projects that are implementing a


JavaScript engine:

 V8 — open source, developed by Google, written in C++


 Rhino — managed by the Mozilla Foundation, open source,
developed entirely in Java
 SpiderMonkey — the first JavaScript engine, which back
in the days powered Netscape Navigator, and today powers
Firefox
 JavaScriptCore — open source, marketed as Nitro and
developed by Apple for Safari
 KJS — KDE’s engine originally developed by Harri Porten
for the KDE project’s Konqueror web browser
 Chakra (JScript9) — Internet Explorer
 Chakra (JavaScript) — Microsoft Edge
 Nashorn, open source as part of OpenJDK, written by
Oracle Java Languages and Tool Group
 JerryScript — is a lightweight engine for the Internet of
Things.

Why was the V8 Engine created?


The V8 Engine which is built by Google and used inside Google
Chrome. It is open source and written in C++. V8 is also used
for the popular Node.js runtime.
V8 was first designed to increase the performance of JavaScript
execution inside web browsers. In order to obtain speed, V8
translates JavaScript code into more efficient machine code
instead of using an interpreter. It compiles JavaScript code into
machine code at execution by implementing a JIT (Just-In-
Time) compiler like a lot of modern JavaScript engines do
such as SpiderMonkey or Rhino (Mozilla).

The main difference here is that V8 doesn’t produce bytecode or


any intermediate code.

V8 used to have two compilers


Before version 5.9 of V8 came out (released in 2017), the engine
had two compilers:
 full-codegen — a simple and very fast compiler that
produced simple and relatively slow machine code.
 Crankshaft — a more complex (Just-In-Time) optimizing
compiler that produced highly-optimized code.

The V8 Engine also uses several threads internally:

 The main thread does what you would expect: fetch your
code, compile it and then execute it.
 There’s also a separate thread for compiling, so that the main
thread can keep executing while the former is optimizing the
code.
 A Profiler thread that will tell the runtime on which methods
we spend a lot of time so that Crankshaft can optimize them.
 A few threads to handle Garbage Collector sweeps.

During initial execution of the JavaScript code, engine


leverages full-codegen which directly translates the parsed
JavaScript into machine code without any transformation. It
allows it to start executing machine code very fast. Since V8
does not use intermediate bytecode representation, hence there
is no need for an interpreter.

Profiler thread is responsible for deciding which method should


be optimized. It does this by gathering data when the code is
running.

Next, Crankshaft optimizations begin in another thread. It


translates the JavaScript abstract syntax tree to a high-level
static single-assignment (SSA) representation
called Hydrogen and tries to optimize that Hydrogen graph.
Note that most of the optimizations are done at this level.
Inlining
The first optimization is inlining as much code as possible in
advance. Inlining is the process of replacing a call site (the line
of code where the function is called) with the body of the called
function. This simple step allows following optimizations to be
more meaningful.

Hidden class
JavaScript is a dynamic programming language which means that properties can
easily be added or removed from an object after its instantiation. For example, in the
code snippet below an object is instantiated with the properties “make” and “model”;
however, after the object has already been created, the “year” property is
dynamically added.

var car = function(make,model) {

this.make = make;
this.model = model;

var myCar = new car(maruti,swift);

myCar.year = 2013;

It’s rather hard to optimize a dynamically typed, prototype-


based language, such as JavaScript. Objects can change their
type during runtime, and it happens implicitly. To track types of
JavaScript object and variables, V8 introduced the concept
of Hidden Classes. During runtime V8 creates hidden classes
that get attached to each object to track its shape/layout. That
allows to optimize the property access time.

Inline caching
V8 takes advantage of another technique for optimizing
dynamically typed languages. It is called inline caching. It relies
on the observation that repeated calls to the same method tend
to occur on the same type of object.

We’re going to discuss only general concept of inline caching.


We will not go into details here for the sake of time.

So how does it work?

V8 maintains a cache of the type of objects that were passed as a


parameter in recent method calls and uses this information to
assume about the type of object that will be passed as a
parameter in the future. If V8 is able to make a good
assumption about the type of object that will be passed to a
method, it can bypass the process of figuring out how to access
the object’s properties. Instead, it uses the stored information
from previous lookups to the object’s hidden class.
So how are the concepts of hidden classes and inline
caching related?

Whenever a method is called on a specific object, the V8 engine


perform a lookup to the hidden class of that object in order to
determine the offset for accessing a specific property. After two
successful calls of the same method to the same hidden class, V8
omits the hidden class lookup and simply adds the offset of the
property to the object pointer itself. For all future calls of that
method, the V8 engine assumes that the hidden class hasn’t
changed and jumps directly into the memory address for a
specific property using the offsets stored from previous lookups.
This greatly increases execution speed.

Inline caching is also the reason why it’s so important that


objects of the same type share hidden classes. If you create two
objects of the same type and with different hidden classes (as we
did in the example earlier), V8 won’t be able to use inline
caching because even though the two objects are of the same
type, their corresponding hidden classes assign different offsets
to their properties.

The two objects are basically the same but the “a” and “b” properties were created in different
order.

Compilation to machine code


Once the Hydrogen graph is optimized, Crankshaft lowers it to a
lower-level representation called Lithium. Most of the Lithium
implementation is architecture-specific. Register allocation
happens at this level.

In the end, Lithium is compiled into machine code. Then


something else happens called OSR: on-stack replacement.
Before we started compiling and optimizing an obviously long-
running method, we were likely running it. V8 is not going to
forget what it just slowly executed to start again with the
optimized version. Instead, it will transform all the context we
have (stack, registers) so that we can switch to the optimized
version in the middle of the execution. This is a very complex
task, having in mind that among other optimizations, V8 has
inlined the code initially. V8 is not the only engine capable of
doing it.

There are safeguards called deoptimization to make the


opposite transformation and reverts back to the non-optimized
code in case an assumption the engine made doesn’t hold true
anymore.

Garbage collection
For garbage collection, V8 uses a traditional generational
approach of mark-and-sweep to clean the old generation. The
marking phase is supposed to stop the JavaScript execution. In
order to control GC costs and make the execution more stable,
V8 uses incremental marking: instead of walking the whole
heap, trying to mark every possible object, it only walks part of
the heap, then resumes normal execution. The next GC stop will
continue from where the previous heap walk has stopped. This
allows for very short pauses during the normal execution. As
mentioned before, the sweep phase is handled by separate
threads.
Ignition and TurboFan
With the release of V8 5.9 in 2017, a new execution pipeline was
introduced. This new pipeline achieves even bigger performance
improvements and significant memory savings in real-
world JavaScript applications.

The new execution pipeline is built on top of Ignition, V8’s


interpreter, and TurboFan, V8’s newest optimizing compiler.

You can check out the blog post from the V8 team about the
topic here.

Since version 5.9 of V8 came out, full-codegen and Crankshaft


(the technologies that have served V8 since 2010) have no
longer been used by V8 for JavaScript execution as the V8 team
has struggled to keep pace with the new JavaScript language
features and the optimizations needed for these features.

This means that overall V8 will have much simpler and more
maintainable architecture going forward.
Improvements on Web and Node.js benchmarks

These improvements are just the start. The new Ignition and
TurboFan pipeline pave the way for further optimizations that
will boost JavaScript performance and shrink V8’s footprint in
both Chrome and Node.js in the coming years.

You might also like