Part I: How to Choose a JavaScript Engine for iOS and Android Development

Jan 17th, 2013

DISCLAIMER: the post contains my personal opinions on the subject. I would appreciate it if you could correct my mistakes.

Back to the time when I started OpenAphid-Engine, there were already several similar iOS/Android projects. These projects, either commercial or open source, expose their core features by JavaScript language. For instance, Titanium and PhoneGap allow developers to use JavaScript to build native iOS/Android apps; ngCore enables building cross platform games by pure JavaScript. JavaScript language has been chosen as a first-class citizen as it’s one of the most popular programming language. It eases the learning curve and easily attracts developers into a new product ecosystem.

How to Support JavaScript on iOS/Android

There are two main approaches to support JavaScript in an iOS/Android app. One method is to leverage the system browser component, UIWebView on iOS and WebView on Android; the other way is to compile and integrate a full-featured JavaScript engine.

Using the system component is easy to implement but it’s inflexible and inefficient. WebView provides addJavascriptInterface to inject Java classes into JavaScript context. But it only supports primitive data types which brings restrictions to API design; it’s also unstable and crashes on Android simulator 2.3 and some real devices according to issue #12987. Things are worse on iOS, UIWebView doesn’t have public APIs to support direct interaction from JavaScript to Objective-C (You have to use private APIs to achieve the same functionality of addJavascriptInterface).

PhoneGap is the most famous project that is built upon UIWebView and WebView. Developers are forced to use callbacks to retrieve return values from its JavaScript APIs, which is complex and inefficient especially for games.

Some earlier versions of ngCore also relied on UIWebView in order to support iOS. This mechanism has been replaced because of the awful performance.

In order to get better performance, flexibility and compatibility, it becomes popular by embedding a full featured JavaScript engine in native apps.

Choices of JavaScript Engines

As far as I know, four JavaScript engines could be built and ran on iOS or Android: JavaScriptCore, SpiderMonkey, V8 and Rhino. The table below lists their compatibilities on iOS and Android.

iOS

Android

JavaScriptCore

Interpreter only

Interpreter and JIT

SpiderMonkey

Interpreter only

Interpreter and JIT

V8

JIT only for jailbroken devices

JIT

Rhino

Unsupported

Interpreter

When I was searching for the right JavaScript engine for OpenAphid-Engine, my evaluation focused on the following metrics:

Compatibility. The engine should support both iOS and Android, and work on both simulators and devices, which requires it support both ARM and x86.

Stability. It should stably work on both platforms and supported CPU architectures.

Extensibility. Extending the engine to add native features should be easy. For example, OpenAphid-Engine required a bridge layer to access OpenGL ES from JavaScript.

Performance. It’s boiled down to two primary factors: fast JavaScript evaluation, and efficient binding mechanism with low overhead. OpenAphid-Engine may trigger hundreds of OpenGL ES calls from JavaScript to render a single frame. The rendering would be slow if the overhead is much more significant than normal JavaScript routines.

Small footprint. The memory footprint and binary size of the executable file should be small.

Rhino and V8 were out first since they don’t support iOS. I really wanted to build OpenAphid-Engine with V8, which showed great performance and elegant code structure during my preliminary experiment on Android. But I got disappointed due to the fact that V8 only employed a JIT mode while iOS doesn’t allow JIT unless on a jailbroken device. Please refer to issue #1312 if you need more technical details.

I debated a lot between JavaScriptCore and SpiderMonkey. After successfully built them on iOS and Android, I applied benchmarks and experiments to find the better one.

SpiderMonkey is available under a more friendly license, but it lost in nearly all of my measurements compared to JavaScriptCore. It generated larger binary file size (about 1.3MB larger for ARMv7); JavaScript evaluation was slower and the performance overhead of bridging JavaScript and C++ was also more significant. One more reason that pushed me away was that my build of SpiderMonkey randomly crashed on iOS simulator.

The performance of a JavaScript engine can be affected by many factors, like the version of build toolchains, the version of engines, and the OS types etc. The chart below lists the running times of several micro-benchmarks with different builds of engines on an iPod Touch 4. Please check out the Google Doc if you’re interested at the precise running times.

Adventure with JavaScriptCore

The running time of 1m-native_function was over six times longer than 1m-js_function and 1m-Math.abs(0) on JavaScriptCore. I also observed the similar performance issue on accessing properties of injected native objects.

The C APIs had a clean design but was lack of flexible memory management APIs. It seemed difficult to resolve issues caused by circular references without deeper cooperation with the internal garbage collector.

I abandoned the original plan of using the C APIs in order to solve problem 1 and 2. The version of JSC from iOS 4.3.3 was used, as it’s faster than the version from iOS 5 in interpreter mode with a smaller binary executable file.

Engines Used in Other Products

During the development of OpenAphid-Engine, I always kept my eyes on other products. The table below summarizes the JavaScript engines they are using underneath.