Moonshine

How to use the JIT compiler

Introduction

Moonshine now has a basic just-in-time (JIT) compiler. When enabled, the JIT compiler monitors the number of invocations of each function and, once a tolerance is met, marks the Lua function to be compiled. The actual compilation phase takes place at a later time, when the process is deemed to idle. Once idle, the queued Lua functions' byte code is translated into JavaScript functions. The interpreter then uses these JavaScript functions for future invocations.

The effect of the JIT compiler is most notable on mobile devices where we have observed around a 4x performance increase on our games.

Please note that the Moonshine JIT compiler is not in any way related to the LuaJIT project and performs a very different process.

The JIT compiler will handle the vast majority of use cases, but unfortunately some features of Lua are not possible in native JavaScript. Therefore, there are currently some limitations to when the JIT can be used. Due to these limitations, the JIT compiler is not enabled by default.

How to enable the JIT

Simply download the latest version of Moonshine and add the following line before loading any code.

shine.jit.enabled = true;

Aside from the known limitations, using the JIT should be seamless.

Configurable options

There are some configurable pseudo-constants that dictate when and how the JIT compiler works:

shine.jit.INVOCATION_TOLERANCE

Default: 2

This is the number of times a function is allowed to run in interpreted mode before it is compiled (on the next call). As there is an overhead to the compile phase, functions are not compiled until they are identified as having been executed enough times to make it worth the overhead. Increase this value to compile less functions and concentrate on only the functions that are used most often.

shine.jit.COMPILE_INTERVAL

Default: 500

When the compilation of a function is deferred, a compile queue is created and an interval starts ticking. On each tick, the FPS since last tick is calculated and compared to shine.jit.MIN_FPS_TO_COMPILE. The shine.jit.COMPILE_INTERVAL constant defines the number of milliseconds between each tick. You may wish to increase this number on processor-intensive scripts.

shine.jit.MIN_FPS_TO_COMPILE

Default: 59

The FPS that is required before the compile queue is processed. Reduce this number to loosen the definition of idle.

If this value is set to zero, compilation is not deferred and instead occurs inline.

Example

The JIT compiler should be configured before any VM has started, like in this example:

shine.jit.enabled = true;
shine.jit.INVOCATION_TOLERANCE = 3;
shine.jit.MIN_FPS_TO_COMPILE = 29;

var vm = new shine.VM();
vm.load('my-script.lua.json');

Limitations

No coroutines

ECMAScript 6 has generators that map nicely to Lua's coroutines. However, we don't live in an ES6 world just yet and there is no construct in today's JavaScript that supports suspending and resuming execution paths.

When execution is interpreted, the interpretor's state can be stored and resumed at a later point in time. Unfortunately there is not the same control over the JavaScript execution threads of compiled functions. For this reason, if you try to use coroutines while the JIT is enabled, an error will occur.

No vm.suspend()

vm.suspend() uses the same method of suspending Lua execution in the interpretor as coroutines. You will get an error if you try to suspend a VM that is running compiled functions.

No debugger

Again, the debugger used the same method of suspending the interpretor to enable you to place breakpoints in your code or step through it. The debugger will not work on functions that have been compiled.