分享

JavaScript Engines: How to Compile Them | Blog | Sencha

 quasiceo 2014-01-23

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/ subdirectory of your WebKit distribution. The Perl script named 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.

There are 18 responses. Add yours.

Loiane

3 years ago

Thanks for this info! Very helpful!

Jay Garcia

3 years ago

this is awesome dude!!!

Sandro Paganotti

3 years ago

definitely useful! which one is the most handy in your opinion ?

Ariya Hidayat

3 years ago

@Sandro: They are all handy smile Just pick one and explore it.

Kevin

3 years ago

Don’t forget Rhino:

http://www.mozilla.org/rhino/

Rubens Mariuzzo

3 years ago

Thanks Ariya, I have never heard about compiling JavaScript Engines neither the related advantages.Thanks, I will give it a shot!

Florian

3 years ago

Please don’t promote SunSpider. It is one of the worst benchmarks out there. It has been designed to test non-optimizing interpreters that were 100 times slower than the current generation of JavaScript engines.
I work with the V8 team and SunSpider is a nightmare for us. We know that improving the numbers on SunSpider will not help normal users (and sometimes even slow down the engine in “normal” code), but on the other hand the benchmark suite is unfortunately very popular. As a result we spend time and energy in improving the SunSpider scores even though the effort could be spent more wisely in other parts of V8.

Joel Hockey

3 years ago

Thanks very much, this is very helpful.

Rhino instructions:

$ cvs -d :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot co mozilla/js/rhino
$ cd mozilla/js/rhino/
$ ant dist

$ java -jar build/rhino1_7R3pre/js.jar
(same as)
$ java -cp build/rhino1_7R3pre/js.jar org.mozilla.javascript.tools.shell.Main

Peter Geil

3 years ago

Thank you very much for sharing this!

Ariya Hidayat

3 years ago

@Kevin: We’ll look into Rhino sometime in the near future, it is not discussed here because Rhino does not power any web browser.

Davi

3 years ago

Is it possible to use a JSEngine in order to make unit tests on Extjs code? - I’m wondering if we could attach such a engine into our IDE and then, before deploying run the code against all known engines. This will be a great speed up for EXT coding….

Cheers,

Davi

Ariya Hidayat

3 years ago

@Davi: Ext code needs more just a bare JavaScript environment, it needs to know about DOM and other stuff which exist in the browser space

Crysfel

3 years ago

This is Gold! a few days ago I was looking something like this, thanks dude!

Wes Garland

3 years ago

Thanks for the great article!

A quick note about SpiderMonkey—1.8.0-rc1 is actually extremely old, and is approximately the JS engine which shipped with Firefox 3.0.x.  Also, the build system is completely different and your instructions won’t work with that release. smile

If you want to profile your application to match a modern Firefox’s JS engine, you should use the hg repository you suggested, and select a revision which matches up with the Firefox release you’re interested in.

If you don’t want to go to the hassle of figuring out what’s-what, Nick Galbreath has sorted that out for you; see http://code.google.com/p/packaging-spidermonkey/

Also, adding—enable-threadsafe—with-system-nspr to the build configuration, and then adding -j to the js shell command line (-j -m for Firefox-4-era SpiderMonkey) will more closely approximate the performance you will see in the browser. You will need NSPR 4.7 or better installed for the threadsafe build. The switches on the shell turn on the JITs (TraceMonkey and JaegerMonkey).

Wes

Ariya

3 years ago

Wes, thanks for the additional information!

Martin

3 years ago

VERY useful article, i installed the three, i’m having a hard time choosing which one to use.

V8 is actually only a tiny bit faster than Tracemonkey, and slower for string concatenation, one aspect of the language that is very important for me.
performance wise these 3 are actually very close (within 20% or so, with my tests)

V8 is easy to build, has a great API, unlike tracemonkey, a real MONSTER, horribly complicated build system, dependence on NSPR etc, and an old C API that is not beyond reproach.

i don’t know the API for JavascriptCore yet, but it’s ridicoulously simple to build, and fast.

so, the remaining issue is the following, i REALLY want a thread safe javascript engine, i want the “onclick” handlers of my application to run in another thread.

V8 is not thread safe at all, Trace and Spider monkey pretend to be thread safe, but are not at all (you can’t access the same object from different threads, so i don’t see the point, i’m not going to be able to explain the users of my project that they have to be careful when they write onclick handlers smile

apparently javascript core is completely threadsafe !

i prefer a threadsafe engine than a fast one, because the user will perceive the threadsafe one to be 100 times faster than the fastest engine, simply because he will never have to wait before the app becomes responsive again.

Kylia

3 years ago

At last, someone comes up with the “right” awnser!

Martin

3 years ago

a follow-up on my previous message:

No Javascript engine is really thread safe, you can’t access the same objects from javascripts with any of them not even JavascriptCore (i was mistaken)

i still decided to use JavascriptCore, because of it’s good performance (as good as any other), very simple API (very similar to spidermonkey, but cleaned up), and because it’s easy to build, especially on a mac.

and, i managed to use it to have multithreaded javascript in my app by doing this :
i have a DOM containing data that i read from an XML file, this is the data i want to script.

Javascriptcore (and the others) are only threadsafe if you use one (group of) context per thread, with different data objects.

it would be too expensive to create javascript objects around each XML node, in each context, so the solution is to create the javascript objects dynamically, from within the callbacks.

that works perfectly, i can modify the properties of the same NODE from different threads, delete and create nodes etc…

Comments are Gravatar enabled. Your email address will not be shown.

Commenting is not available in this channel entry.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多