DWScript Just-In-Time compiler

dws-mirrorFor a couple months now, a JIT compiler has been available in the DWScript SVN for 32bits code. The JIT is meant to be transparent, that is, execution, debugging facilities and memory layouts are similar under JIT and interpretation, the only difference is the performance.

The JIT also uses a hybrid architecture, which was designed to allow developing the JIT incrementally, which directly derives from my only working on it a few hours at a time, every once in a while.

Here is a summary of how it works, what it can and can’t do… yet, on the way to the next level in DWScript programming 🙂

Greedy JIT compiler

To use the greedy JIT compiler,  just compile a script as usual, and then with the IdwsProgram you obtain, invoke the JIT:

jit:=TdwsJITx86.Create;
try
   jit.GreedyJIT(prog.ProgramObject);
finally
   jit.Free;
end;

And that’s all. All executions of your program will now benefit from native compilation.

How it works

The JIT compiler goes through the Abstract Syntax Trees of the script program, and replaces all the nodes it knows how to JIT with natively compiled snippets. What this means is that not everything may be JIT’ed, the JITter is still a work in progress, and ability to compile more nodes and handle more cases is getting added incrementally.

If all the nodes in your script are JIT’able, then your script will become a snippet of native code and runtime performance will be high. If only some nodes are JIT’able, then your mileage may vary as the saying goes, from gaining a few percents over the regular execution engine, to seeing major speedups if your bottlenecks get JIT’ed.

As the JITter gets improved and learns more tricks (and can compile more AST nodes), performance will progressively go up.

Current strong and weak points

Take this paragraph as a “snapshot” of what the JIT can do right now.

Strong points:

  • floating point operating on local variables, records and static arrays is fast and leverages SSE2
  • if/then boolean evaluations, while/repeat until, for (upward) loops
  • JIT compilation is very fast, as register allocation is a single-pass greedy affair

Weak points (no or very partial JITting yet):

  • functions and method calls, var parameters
  • dynamic arrays, classes, interfaces, strings
  • register allocation isn’t very sophisticated, relies on a high number of registers, and so under 32bits, works best on floating point

Another structural limitation for the JIT is that the storage units are still variants, which results in greater memory dispersion of the data. This is another reason why Variants are on the way out in DWScript, though the road is still long.

Script debugging support

JIT’ed scripts are debuggable as usual and steppable if you specify the jitoDoStep JITter option.

So you can use JIT’ed scripts directly with existing debugging components and IDEs with no changes. Values can be inspected, breakpoints can be set, etc.

Future evolutions

Better JITting for dynamic arrays is high on the priority list, as one of the early goals is to provide fast maths, this largely done for floating point arrays.

A future 64bit JITter should (eventually) be the one with the most support and best performance. The 32bit JITter was started first as the Delphi 32bit debugger is more stable, so it’s simpler to validate approaches and strategies.

In terms of performance, the goal is to be in the same ballpark as other JITters out there, and comparable to “simple” native compilers, while maintaining a very high JIT compilation speed.

Contrarily to the LLVM effort, the goal here is strictly aimed at faster scripting.