I've been researching the V8 engine and JavaScript engines in general for about 2 hours, and I was wondering if I have the process correct.
First, we have JavaScript code.
With the code as input, the CPU runs the JavaScript engine, which in turn starts the process.
The code is parsed through the Parser
The Parser outputs an Abstract Syntax Tree.
The AST is then passed through the Interpreter, where a few things happen.
- The Interpreter evaluates the AST (code) and returns an output
- (*)JIT compilation converts the output to byte code.
This byte code is run directly, as the interpreter doesn't output any machine code.
(*) - This is a part where I am basically guessing. It's hard to understand the differences between the interpreter and compiler, since... isn't JavaScript an intrepted language? What is the compiler even doing? Optimizations? On what? And sometimes I see people say it's not just doing optimizations. I don't really know...
Any assistance would be greatly appreciated.
(V8 developer here.)
There are (at least) two distinct concepts here:
(1) JavaScript being an interpreted language means that JavaScript programs are delivered to those who want to run them in the form of source code. This is in contrast to e.g. C++, which is (almost always) compiled to machine code by the developer, and then distributed as machine code (a.k.a. "binaries" or "executables", meaning "executable by hardware").
For JavaScript's primary use case on websites, this is an advantage, because machine code is specific to a hardware platform, whereas websites are meant to run on any client hardware. By distributing JS as source, the browser takes care of what's needed to run it on your particular device (x86 or ARM or MIPS or ..., 32 or 64 bits, SIMD support or not, ...).
In accordance with this characterization of the JS language, sometimes the term "JS interpreter" is used interchangeably with "JS engine" and "JS virtual machine", meaning "any software that executes JavaScript" (regardless of how exactly it is implemented under the hood).
(2) An "interpreter" inside a (JavaScript or other) engine as opposed to a JIT compiler.
For programming languages that are distributed as source code, engines can still choose to generate machine code on the fly, this is called "just in time compilation", as opposed to executing the program on an "interpreter" that does not rely on generating machine code. Instead, such an interpreter is a program that simulates a machine that can execute the programming language in question directly. (This can be a bit difficult to grasp at first; see e.g. this answer for an example of a very simple interpreter for a made-up language.)
For example, @Pointy's comment that says:
refers to concept (1), because an interpreter in the sense of (2) does not produce machine code, but an "interpreter" in the sense of "JS engine as used in today's browsers" certainly does (when a function is hot).
With that general framing out of the way, I have some answers to your specific question, specifically in V8:
Almost: the AST is compiled to bytecode (just once), this bytecode is then interpreted (as often as the function is called). That's it. The result of the interpretation is that the program is getting executed, which could e.g. insert a DOM node or print something to the
consoleor whatever.It generates optimized machine code for a given function.
If and when the interpreter realizes that it's spending a lot of time interpreting a particular function, it instructs the optimizing compiler to compile this function to optimized machine code. The compiler runs in the background, and takes some time (usually on the order of milliseconds, but potentially hundreds of them) to:
Array.pushwith shortcuts when circumstances allow)There are several reasons for having such a multi-tier execution system, three of the most important ones are:
No, machine code (optimized or not) is executed directly on the CPU, not on the interpreter.
For completeness, it gets even more complicated: