If you use Javascript, you might already know what is a V8 engine, if not, you will come across this word in the future. V8 is a javascript engine build by Google for Chrome and now Microsoft Edge also uses it.
This blog post will give you an idea of how the V8 engine works.
Table of Content
- Javascript Engine
- Compiled or Interpreted?
- V8 Engine
- Parsing
- Ignition
- TurboFan
- Assumptions
- Conclusion
Javascript Engine - What and Why?
Let's take a step back and talk about engines in general.
Do we need a Javascript Engine? What's the purpose of a Javascript?
Javascript engine helps to convert Javascript to machine-readable code. That's why the Javascript engine is something very essential to execute your code.
Each browser uses a Javascript engine. Here are a few of them.
- V8 (Chrome, Microsoft Edge and Nodejs)
- Chakra (Internet Explorer)
- SpiderMonkey (Mozilla Firefox)
- JavascriptCore (Safari)
Compiled or Interpreted?
There are two ways in which a language is translated into a machine-readable form.
- An interpreter helps interpret the language line by lines such as Python and PHP.
- A compiler helps to compile the whole code to machine language and then execute it.
Interpretation helps in faster execution but lack of optimization. Whereas, compilations help in optimization but is slow when it comes to execution because it first reads the whole file and converts it to a machine-readable format.
Javascript uses the best of both worlds and makes a JIT (Just in Time) compilation. This helps in faster execution and optimized code.
V8 Engine
V8 is a javascript engine developed by Google. It's an open-source project, written in C++. V8 can be used as standalone too.
There are several steps that the V8 engine takes to convert your code.
Parsing
The first step is to convert into AST (Abstract Syntax Tree). The V8's parser does that job, it takes the code and parses it into AST.
Abstract Syntax Tree is a tree representation of the source code.
There are two steps in this phase,
- Lexical Analysis
- Syntactical Analysis
Lexical Analysis
Before we parse the code into an Abstract syntax tree, we first convert it into Tokens. This conversion to tokens happens in Lexical Analysis.
A Scanner consumes a stream of Unicode characters, combine it into tokens, and remove all the whitespace, newlines and comments, etc.
These tokens are keywords, operators, etc.
Syntax Analysis
Once the engine converts your code into tokens, it's time to convert it into Abstract Syntax Tree. This phase is called Syntax Analysis.
The tokens are converted into Abstract Syntax Tree using V8's Parser and the language syntax validation also happens during this phase.
Ignition
The heart of the V8 engine is Ignition and TurboFan.
Ignition is the component that helps to interpret your bytecode.
Once the engine has AST, it sends this tree to Ignition which converts it into bytecode. Then this bytecode is interpreted by a high-performance interpreter.
Ignition has a swift startup time, and the bytecode it produces is very small, so V8 uses it to execute the code on page load. Ignition is used for the infrequent code because the performance comes at a cost and V8 doesn't want to consume a lot of memory.
TurboFan
TurboFan is an optimizing Compiler, which compiles your code to an optimized Machine language. It generates an extremely fast Machine Code. It does this with the help of assumptions (we will get into this in a while).
Since the TurboFan generates an optimized Machine Code, the V8 uses TurboFan to produce an optimized version of frequently used code.
Assumptions
Now you have an idea of Parser, Ignition, and TurboFan and you also know how they work. Let's go a little deeper into how Ignition and turboFan work together to provide fast and optimize code.
Before we dive into it, let's first understand a term Hot Code. Any function or a chunk of code that is repeated several times is called Hot Code. For example
while(i == 1000) {
doAwesomeThings();
i++;
}
This code snippet will execute function doAwesomeThings
thousand times, so its a frequent code (Hot Code).
Now let's understand how Ignition and TurboFan work together.
When Ignition interprets the bytecode.
- It gathers Type Feedback, which is the information of types.
var numbers = [1,2,3,4,5]
numbers.map(x => x + 2);
Type Feedback: x is a Number
- If the function is running frequently, it detects Hot Code.
- Ignition send the Type Feedback to TurboFan
- With the help of Ignition's type feedback, TurboFan makes optimistic assumptions to produce optimized code.
That map function which you saw above has an addition operator and according to EcmaScript, these are the steps for the addition operator
You see it checks for the types and then makes a decision but when Ignition does the Type Feedback, it crosses out some part of the algorithm. Like in the case of our Map function, with the help of Ignition's Type Feedback, TurboFan will assume that the x will always be a number, so it optimizes the code by removing some part of spec level code.
This way the engine used the optimized version to execute your code.
Now, what if the array has a string in it?
The assumption will fail and Ignition will resume execution.
Conclusion
So to conclude the whole concept, the way V8 Engine works is
- Code is parsed to AST by a Parser
- Ignition converts it into Bytecode and an interpreter reads it (This Interpreter is in Ignition)
- If Ignition finds a Hot function, it makes an assumption and sends it to TurboFan.
- TurboFan uses these assumptions to generate an optimized version of that code.
Note: There is more than just Ignition and TurboFan, I did not cover things like Garbage collection, Function Call Stack, Heap, etc. That is for another day.
Top comments (3)
@arslan - good post.
To quickly understand the V8 architecture, I like this picture from @addy .
Check out v8.dev. Very interesting developments are happening to improve handling WebAssembly.
With V8, WASM & HTTP3/QUIC protocol, the web as we know it will become faster and a lot more fun!
Thanks for posting this amazing illustration. Addy Osmani is amazing guy.
Hello, I think there is a mistake in the last image.
Should have a "5 Return Number:add(lval, rval)*"?