Is JavaScript an interpreted language or a compiled language? This has been a long-standing debate. The author of YDKJS (You Don't Know JS) concluded that he thinks it's a compiled language. After researching why he thought so, I've reached my own conclusion as stated in the title: JavaScript is both an interpreted and a compiled language.
Modern JavaScript is executed by various engines. Let's look at how these engines execute JavaScript to understand why I reached this conclusion.
Background Knowledge
JIT Compiler
Unlike traditional compilation methods that compile all code before execution, JIT (Just-In-Time) compiles code at the moment it's needed during program execution. Instead of pre-compiling all source code, it optimizes performance by immediately compiling only the necessary parts at runtime.
Intermediate Representation (IR)
Intermediate Representation (IR) is an intermediate code or data structure form that compilers or interpreters create internally when processing source code. Before directly converting source code to machine code or executing it, the source code is transformed into a simpler, more abstract form that allows compilers or interpreters to analyze and optimize more easily.
V8
Chrome's V8 JavaScript engine operates with 1 interpreter and 3 compilers. Let's briefly look at how it works.
1. Ignition (Bytecode Interpreter)
The original V8 engine improved speed by JIT compiling all JavaScript code into native machine code just before execution. Initially, it used the Baseline compiler for quick compilation. Since code compiled through the Baseline compiler wasn't optimized, it was recompiled by advanced compilers like TurboFan through V8's execution pipeline.
The problem with this approach was that it wasted memory by compiling even code that only ran once. To mitigate this overhead, they created a new JavaScript interpreter called Ignition to replace the existing Baseline compiler.
Ignition first converts JavaScript code into concise bytecode, which is then executed by the interpreter.
There's a presentation video about this, which would be good reference for those who want to deep dive!
2. Sparkplug (Baseline JIT)
Although they improved performance by creating the Ignition interpreter, they eventually hit the limits of interpreter characteristics. So in 2021, they created a compiler called Sparkplug that operates between Ignition and the advanced compiler TurboFan.
Sparkplug doesn't compile JavaScript but compiles the bytecode that the interpreter has already created. Also, without an Intermediate Representation (IR), it generates machine code directly by scanning the bytecode just once.
Sparkplug creates quickly executable code without going through complex optimization processes. More complex optimizations are handled by TurboFan.
3. Maglev (Fastest Optimizing JIT)
In 2023, another new compiler appeared. The Ignition interpreter is fast to execute but low in performance, Sparkplug almost immediately converts bytecode to machine code but has limited optimization levels, and TurboFan creates the highest-performance optimized code but takes a long time to compile. To break these limitations once more, they created a compiler called Maglev.
Maglev is the latest JIT compiler introduced in Google's V8 JavaScript engine, positioned between the existing Sparkplug and TurboFan. Maglev's goal is to generate "good enough" optimized code very quickly.
4. TurboFan (High-Level Optimizing JIT)
TurboFan is V8's top-tier optimization compiler. It performs the most complex and sophisticated optimizations to produce the highest-performance machine code.
But TurboFan's disadvantage is that it takes a long time to compile. So it's only applied to "HOT 🔥" code that runs very frequently. V8 tracks which code runs frequently through profiling and only recompiles such code with TurboFan.
In this way, V8 selects the optimal execution strategy for JavaScript code characteristics through a 4-stage execution pipeline.
Webkit
Webkit also operates with 1 interpreter and 3 compilers.
1. LLInt (Low Level Interpreter)
LLInt is the first execution stage of the WebKit JavaScript engine. It converts JavaScript code to bytecode and then reads and immediately executes the bytecode line by line. If a specific function or code is executed multiple times ("HOT 🔥" code), it attempts to compile it using Baseline JIT.
2. Baseline JIT
When code is detected to be executed multiple times in LLInt (function 6 times, specific command 100 times), it converts the bytecode almost directly to machine code. Since compilation speed is very fast, there's almost no execution delay. It doesn't perform optimization.
3. DFG JIT (Data Flow Graph JIT)
It operates when a function is called more than 66 times or a specific command is executed more than 1000 times in Baseline JIT. It converts bytecode to an intermediate representation (IR). DFG JIT analyzes the data flow of code to perform various compiler optimizations such as removing unnecessary operations, inferring variable types, and allocating registers.
4. FTL JIT (Faster Than Light JIT)
This is the most advanced compiler used in WebKit. It maximizes execution speed by converting JavaScript code to native code.
SpiderMonkey
Firefox's SpiderMonkey also strategically selects and uses interpreters and various JITs in a similar way.
Conclusion
I've examined the three most widely used engines. All engines were using a mix of interpreter and compiler methods. They start quickly with an interpreter and gradually compile to improve performance as needed.
I think the question "Is JavaScript an interpreted language or a compiled language?" itself is not appropriate in modern JavaScript execution environments. It seems more accurate to describe modern JavaScript as "a language that uses a mix of interpreters and compilers."
References
- Interpreter (computing) - Wikipedia
- Firing up the Ignition interpreter · V8
- Maglev - V8's Fastest Optimizing JIT · V8
- Sparkplug — a non-optimizing JavaScript compiler · V8
- Introducing the WebKit FTL JIT
- Introducing the B3 JIT Compiler
- The Baseline Interpreter: a faster JS interpreter in Firefox 70 – Mozilla Hacks - the Web developer blog
- Warp: Improved JS performance in Firefox 83 – Mozilla Hacks - the Web developer blog