How types save execution time - a v8 engine overview


Is JavaScript a compiled language or an interpreted one? it's a mix of both, we call it just-in-time compiled language.
Compiled languages first compile the whole code from High-level to machine code and then execute it, making it a bit time consuming initially but executes fast. Meanwhile the interpreter languages go line by line and have a fast initial execution. JS uses the best of both worlds, let's look at how v8 engine handles it.
Here's a diagram which kind of sums up the whole if you are in a rush:
First up: Tokenization & Parsing
At first, a lexical analysis
of the code breaks it down into tokens, these make it easier for parsing. The tokens are further passed down to a parser for syntax analysis
to build an AST (Abstract Syntax Tree).
An AST looks something like this for a variable declaration like:
let a = 10;
We can observe how the identifier "a" and literal value "10" are stored within a VariableDeclaration. Checkout this site where you can play around the code to see how it is built out
If you didn't catch it, when an Abstract Syntax Tree is incorrectly built out, that is when we get a
Syntax Error
while writing a program.
Interpreter and Compiler
After the Abstract Syntax Tree is built out, we pass it up to the interpreter for execution. Interpreters directly execute the code sequentially. Our v8 engine has two core components namely
Ignition Interpreter which converts the AST into byte code
TurboFan Compiler which gives optimized machine code for quick execution
So when is the TurboFan compiler used if the interpreter executes the code instantly?
Case 1: commonly used code
If there is a repeatedly used piece of code, commonly a function, it doesn't make sense for the interpreter to keep working on it each time it is used isn't it? So, the Ignition interpreter sends it over to the TurboFan compiler to get an optimized machine code and execute it each time it is required in the code.
Case 2: Recurring code but with wrong types
The first time a piece of code, like let's say a sum(1,2)
is passed to the compiler, initially it will assume the types for it to be integers and make a machine code optimized for that specific use case.
If we happen to pass a string like sum("a", "b")
then the compiler is forced to generate machine code again to process a string based input and not for an integer.
As we can see, a wrong type passed down a
sum()
causes an extra step in the process to generate the desired output. Thus, it is always recommended to use types intended for a variable / function etc.
And that's how google's V8 engine handles our code which is used in chromium browsers and Node.js. We have other alternative engines like Firefox's SpiderMonkey, Apple / safari's JavaScriptCore, they differ in architectures slightly but have a JIT compilation as a common theme.
Checkout this video for a deeper dive and be awe-struck by how well this is all structured if you are not impressed already.
Credits: This blog is a simplified explainer thanks to a course called namaste node.js, highly recommend checking it out.
Recommended further reading:
PS: JS engine breakdown video from the man Akshay Saini himself (I didn't know he had a standalone video on yt as well before writing this article haha)
Subscribe to my newsletter
Read articles from Shreyas directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
