I was recently looking into some code optimization behavior by V8, the JavaScript engine used by Chrome and Node. (The JavaScript engine in Firefox is called SpiderMonkey, the one in Internet Explorer Chakra).
Modern JavaScript engines don’t interpret the JavaScript code directly, but instead translate it to machine code that can run much faster.
This post explains how to compile V8 and and use the JavaScript shell it provides (called D8).
Compiling V8
Update: Consider using JSVU to install D8 instead.
Before compiling V8 you need to install the Chromium depot_tools - when I tried it without V8 complained that it couldn’t find a program called gclient:
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=`pwd`/depot_tools:"$PATH"
Then check out the V8 repo and build the JavaScript engine:
svn co http://v8.googlecode.com/svn/trunk v8-trunk
cd v8-trunk
make dependencies
make ia32.release objectprint=on disassembler=on
export PATH=${PATH}:`pwd`/out/ia32.release
I’m not sure what exactly objectprint does and if it’s necessary, but you need the disassembler options to be able to view the assembly code generated by V8.
Now the D8 program should be available in your console.
Running a JavaScript program with D8
The D8 command line tool is very similar to the node
command. You can ither use the interactive shell or use it to execute a specific file.
For example if we have this code in add.js:
Then you can run it by calling
d8 add.js
And the output will be
34
Not how we use print
rather than console.log
. The console
API isn’t available natively in the V8 engine.
Using D8 to view the Hydrogen code representation
Update: I don’t think V8 uses Hydrogen anymore, but you can view the interpreter bytecode with --print-bytecode
.
There are four main stages that code inside V8 passes through:
- JavaScript Code - this is what you wrote
- Hydrogen - intermediate code representation where optimizations are applied
- Lithium - machine specific code used to generate native code
- Machine Code - this is what the computer understands
The --trace-hydrogen
option allows you to see the Hydrogen code that’s generated when running V8. To ensure the Hydrogen code is created for the add
function, add this to the code:
Now d8 --trace-hydrogen add.js
will create a hydrogen.cfg file in the current directory.
The file is 80KB large and contains code like this:
begin_compilation
name "add"
method "add:0"
date 1440029178000
end_compilation
begin_cfg
name "H_Assign dominators"
begin_block
name "B0"
from_bci -1
to_bci -1
predecessors
successors "B1"
xhandlers
flags
loop_depth 0
begin_states
begin_locals
size 0
method "None"
end_locals
end_states
begin_HIR
0 0 v0 BlockEntry type:Tagged <|@
0 0 t12 Constant 0x333080a1 <the hole> [map 0x29e08211] <|@
You can have a look at the entire file on Github.
Using D8 to view the generated assembly code
Update: I’m not sure if --print-code
still works with the instructions above, but it should still work if built in Debug mode.
Finally, you can use the --print-code
option to see the native code generated by V8:
d8 --print-code add.js
Which will output something like this:
--- Raw source ---
function add(a, b){
return a + b;
}
...
--- Code ---
source_position = 0
kind = FUNCTION
Instructions (size = 456)
0x36953100 0 8b4c2404 mov ecx,[esp+0x4]
0x36953104 4 81f991806049 cmp ecx,0x49608091 ;; object: 0x49608091 <undefined>
0x3695310a 10 750a jnz 22 (0x36953116)
0x3695310c 12 8b4e13 mov ecx,[esi+0x13]
0x3695310f 15 8b4917 mov ecx,[ecx+0x17]
0x36953112 18 894c2404 mov [esp+0x4],ecx
0x36953116 22 55 push ebp
You can have a look at the full assembly code for add.js on Github.