V8 is Google’s open source JavaScript engine. Chrome, Node.js, and many other applications use V8. This article explains V8’s bytecode format — which is actually easy to read once you understand some basic concepts.
This post isavailable in Chinese, translated by.
When V8 compiles JavaScript code, the parser generates an abstract syntax tree. A syntax tree is a tree representation of the syntacticstructure of the JavaScript code. Ignition, the interpreter, generates bytecode from this syntax tree. TurboFan, the optimizing compiler, eventually takes the bytecode and generates optimized machine code from it.
If you want to know why we have two execution modes, you can check out my video from JSConfEU:
Bytecode is an abstraction of machine code. Compiling bytecode to machine code is easier if the bytecode was designed with the same computational model as the physical CPU. This is why interpreters are often register or stack machines.Ignition is a register machine with an accumulator register.
You can think of V8's bytecodes as small building blocks that make up any JavaScript functionality when composed together. V8 has several hundred bytecodes. There are bytecodes for operators likeAdd
orTypeOf
, or for property loads likeLdaNamedProperty
. V8 also has some pretty specific bytecodes likeCreateObjectLiteral
orSuspendGenerator
. The header filebytecodes.h defines the complete list of V8’s bytecodes.
Each bytecode specifies its inputs and outputs as register operands. Ignition uses registersr0, r1, r2, ...
and an accumulator register. Almost all bytecodes use the accumulator register. It is like a regular register, except that the bytecodes don’t specify it. For example,Add r1
adds the value in registerr1
to the value in the accumulator. This keeps bytecodes shorter and saves memory.
Many of the bytecodes begin withLda
orSta
. Thea
inLda
andSta
stands foraccumulator. For example,LdaSmi [42]
loads the Small Integer (Smi)42
into the accumulator register.Star r0
stores the value currently in the accumulator in registerr0
.
So far the basics, time to look at the bytecode for an actual function.
function incrementX(obj) {
return 1 + obj.x;
}incrementX({x: 42}); // V8’s compiler is lazy, if you don’t run a function, it won’t interpret it.
If you want to see V8's bytecode of JavaScript code, you can print it by callingD8 or Node.js (8.3 or higher) with the flag
--print-bytecode
. For Chrome, start Chrome from the command line with--js-flags="--print-bytecode"
, seeRun Chromium with flags.
$ node --print-bytecode incrementX.js
...
[generating bytecode for function: incrementX]
Parameter count 2
Frame size 8
12 E> 0x2ddf8802cf6e @ StackCheck
19 S> 0x2ddf8802cf6f @ LdaSmi [1]
0x2ddf8802cf71 @ Star r0
34 E> 0x2ddf8802cf73 @ LdaNamedProperty a0, [0], [4]
28 E> 0x2ddf8802cf77 @ Add r0, [6]
36 S> 0x2ddf8802cf7a @ Return
Constant pool (size = 1)
0x2ddf8802cf21: [FixedArray] in OldSpace
- map = 0x2ddfb2d02309 <Map(HOLEY_ELEMENTS)>
- length: 1
0: 0x2ddf8db91611 <String[1]: x>
Handler Table (size = 16)
We can ignore most of the output and focus on the actual bytecodes. Here is what each bytecode means, line by line.
LdaSmi [1]
loads the constant value1
in the accumulator.
Next,Star r0
stores the value that is currently in the accumulator,1,
in the registerr0
.
LdaNamedProperty a0, [0], [4]
LdaNamedProperty
loads a named property ofa0
into the accumulator.ai
refers to the i-th argument ofincrementX()
. In this example, we look up a named property ona0
, the first argument ofincrementX()
. The name is determined by the constant0
.LdaNamedProperty
uses0
to look up the name in a separate table:
- length: 1
0: 0x2ddf8db91611 <String[1]: x>
Here,0
maps tox
. So this bytecode loadsobj.x
.
What is the operand with value4
used for? It is an index of the so-calledfeedback vector of the functionincrementX()
. The feedback vector contains runtime information that is used for performance optimizations.
Now the registers look like this:
The last instruction addsr0
to the accumulator, resulting in43
.6
is another index of the feedback vector.
Return
returns the value in the accumulator. That is the end of the functionincrementX()
. The caller ofincrementX()
starts off with43
in the accumulator and can further work with this value.
At a first glance, V8’s bytecode might look rather cryptic, especially with all the extra information printed. But once you know that Ignition is a register machine with an accumulator register, you can figure out what most bytecodes do.
Learned something? Clap your 👏 to say “thanks!” and help others find this article.
Note: The bytecode described here is from V8 version 6.2, Chrome 62, and a (not yet released) version of Node 9. We always work on V8 to improve performance and memory consumption. In other V8 versions, thedetailsmightbe different.
Senior Engineering Manager at Google. Node.js Monkey Patcher.