JavaScript Engines: How to Compile Them
October 12, 2010 | Ariya Hidayat
The unique thing about JavaScript compared to other scripting environments, is that it’s already deployed to billions of Internet users out there. If you are using a modern browser, you already have a powerful and high-performance JavaScript engine at your disposal.
With growing demand for out-of-browser JavaScript (e.g. server JavaScript), a good knowledge of JavaScript engines is becoming more important. When we want to embed and extend JavaScript, we need a good understanding of its features and limitations. But for this purpose, the JavaScript engine within the web browser may as well be a black box.
In this blog entry, I’ll start to open the box for you a little, and show you how to compile the three major open-source JavaScript engines outside the browser. Since the engines are all written in C/C++, you’ll need some basic knowledge of these languages and a suitable compiler + platform (my preference is *nix). The good news is that compiling the engines only takes a few minutes, once you’re set up.
JavaScript Engines From 10,000 feet
A typical JavaScript engine uses these building blocks: parser, interpreter, and run-time module.
The parser analyzes the source code and constructs a syntax tree to represent the flow of the code. Usually the parser starts by splitting the code into tokens, i.e. classifying whether a word is a string literal, a number, a keyword, a variable name, and interpreting punctuation (curly braces, semicolon etc.). From this token list, the grammatical structure of the program can be inferred, since every JavaScript program needs to adhere to a certain grammar.
The interpreter uses the syntax tree to execute the code. Interpreter implementations vary widely. Nowadays, a fast JavaScript interpreter performs many optimizations before executing the code, in order to achieve maximum speed.
The run-time module provides standard objects such as String, Math, Array, etc.
A JavaScript engine also needs to interface with the rest of the world, particularly when it is embedded in a browser or another application. For this purpose, there are bindings. Bindings allow the embedding program to pass object references to the JavaScript world and vice versa. A classic example is when you manipulate DOM using JavaScript: what’s really happening is that JavaScript code is binding to a non-Javascript browser object (a DOM element) which lives inside the browser.
In a future installment, I’ll will look at the various building blocks of a JavaScript engine in more detail. Now, on to building our own engines.
SpiderMonkey
SpiderMonkey is the JavaScript engine behind Mozilla applications, including of course Firefox. Its lineage can be traced back to the very first JavaScript engine created by Brendan Eich for Netscape.
To get SpiderMonkey source code, you can either use the release distribution or Mercurial. At the time of this writing, the latest development release is 1.8 RC1. If you want to stay bleeding-edge, use your favorite Mercurial client to get the source code as follows:
hg clone http://hg.mozilla.org/mozilla-central/ cd mozilla-central/js/src/
This essentially grabs the entire Mozilla source code. SpiderMonkey itself is in the js/src
subdirectory.
To compile it, just run the following:
autoconf213 ./configure --disable-debug --enable-optimize make
Note that autoconf version 2.13 is required, so any other version might not work. For Mac OS X, you can get it via MacPorts, i.e. running sudo port install autoconf213
. Many Linux distributions also offer autoconf 2.13 under various package names (e.g. autoconf213
on OpenSUSE), use your package manager to find it.
If nothing goes wrong, you will have a mini JavaScript shell executable shell/js
. If you launch it from a terminal, you can interact with the interpreter:
~/mozilla-central/js/src > shell/js js> print("Hello, SpiderMonkey") Hello, SpiderMonkey js> var x = 42; print("x is " + x); x is 42 js> quit()
As you can see, the mini interpreter has several built-in functions, notably print() and quit().
If you face a problem and/or want to know more in details, refer to the official build documentation.
JavaScriptCore
Another popular engine is JavaScriptCore, which is part of WebKit. Many WebKit-based browsers, such as Safari, uses JavaScriptCore as their JavaScript engine.
You need to use Subversion to check out the code:
svn checkout http://svn./repository/webkit/trunk webkit cd webkit
or use Git to clone it from the git mirror:
git clone git://git./WebKit.git webkit cd webkit
The build process depends on your platform. Because the engine was was originally developed by Apple, it is very easy to build it on Mac OSX. On Mac OSX, just use XCode to open JavaScriptCore/JavaScriptCore.xcodeproj
and build both the JavaScriptCore and jsc targets. The latter is the mini interpreter (see build/Release/jsc
for the output).
When it is built, you can launch the shell and get the interactive JavaScript console:
~/webkit/JavaScriptCore > build/Release/jsc > print("Hello, JavaScriptCore!"); Hello, JavaScriptCore! > quit();
If you are not using Mac OS X, you can use one of the other WebKit ports compatible with your system. For example, QtWebKit runs just fine on Linux and Windows (among others). Since we are only interested in the JavaScript engine, just build that part and skip the build steps for WebKit library. The build process in this case is (start from top-level source tree):
qmake -r DerivedSources.pro cd JavaScriptCore make -f Makefile.DerivedSources qmake && make qmake jsc.pro && make
The mini interpreter will be built as a jsc
executable in the same directory.
Note: Obviously, you need Qt libraries before you can do the above steps. Almost every Linux distribution offers a ready-to-use binary package, usually under the name libqt4-devel
or libqt4-dev
or some variation thereof. You also have the option to download and install it from the official Qt web site.
V8
Compared to the previous two, V8 is the youngest engine and was developed by Google to power the Chrome browser.
The code can be obtained from its official repository using Subversion:
svn checkout http://v8./svn/trunk/ v8 cd v8
or use Git to clone it from the mirror:
git clone git://github.com/v8/v8.git v8 cd v8
V8 uses scons as its build system so make sure you have it. Mac OSX users can get that easily via MacPorts. Linux users should be able to install it using the package manager.
The build steps are very simple:
scons sample=shell mode=release snapshot=on
The executable named shell
is the actual mini interpreter. It works just like the one from SpiderMonkey and JavaScriptCore above:
~/v8 > ./shell V8 version 2.4.8 (candidate) > print("Hello, V8"); Hello, V8 > quit();
Running Benchmarks
Now that we have all three JavaScript engines compiled, for the fun of it we can compare their performance by running a benchmark. SunSpider is probably the best known benchmark, and tests a wide variety of text, data-structure, cryptographic and other functions. It measures the time spent executing each individual small test, for a certain number of runs (default to 5), and then print out the result along with the error range. It is important to watch the error range, if it is too high than the benchmark result is not statistically sound, for example when it is executed under varying CPU load.
Often SunSpider benchmark is started from a web browser (since it is the easiest way to test the browser’s JavaScript engine). However, we can always run it from a command line, especially if we have the mini interpreter. SunSpider is already included in the SunSpider/sunspider
will run the test.
Note: Before you run any benchmarks, make sure all the engines are built using the “optimized for release build” compiler settings. Otherwise, any comparison will be unfair.
To run SunSpider tests with SpiderMonkey, run the following:
perl sunspider --shell=/path/to/mozilla-central/js/src/build-release/js --args=-j
The last argument is needed so that the garbage collector can be explicitly triggered.
To test JavaScriptCore, the command is:
perl sunspider --shell=/path/to/webkit/JavaScriptCore/jsc/jsc
And finally, with V8, use:
perl sunspider --shell=/path/to/v8/directory/shell --args=-expose-gc
Of course, besides running benchmarks, building your own JavaScript engine provides other benefits. Once you’re running your own engine, you can instrument and script it to understand how it works and learn the magic behind many of its advanced optimizations.