Skip to content
Tech News
← Back to articles

Retrofitting JIT Compilers into C Interpreters

read original more articles
Why This Matters

This article highlights a significant advancement in the performance of C interpreters by integrating JIT compilation with minimal code changes, potentially transforming language implementation and execution efficiency. Although still in early stages, this approach could enable faster, more compatible language runtimes, benefiting both developers and end-users by bridging the gap between interpretive flexibility and high performance.

Key Takeaways

C interpreters are a common language implementation technique and the basis for the reference implementations of languages such as Lua, Ruby, and Python. Unfortunately, C interpreters are slow, especially compared to language implementations powered by JIT compilers. In this post I’m going to show that it is possible to take C interpreters and, by changing a tiny proportion of code, automatically turn them into JIT compiling VMs (Virtual Machines) . This offers a point in the language performance design space that was previously out of reach: better performance while retaining full compatibility with reference implementations.

The system that lets us do this is yk, something we’ve have been working on for some time. I want to set expectations appropriately. yk is alpha-stage software: it’s not production ready, though it’s some way beyond a hacky research prototype in breadth and quality. You can quite easily hit TODO s that halt the program, or encounter missing parts where execution continues suboptimally; we only have a subset of the optimisations we’d like; only an x64 is currently implemented; and we don’t yet have the breadth of languages and benchmarks that one would need to draw strong conclusions about performance.

That said, you can already see some interesting results. Seeing programming language performance is quite hard, but this video might give you an idea. First, we run a Mandelbrot program through the reference PUC Lua VM and then through yklua, our fork of PUC Lua with an automatically derived JIT compiler:

[Video]

I must confess that I have cherry picked an example where we get better-than-average performance. Rather than the ~4x improvement in the video above, on our Lua benchmark suite so far a more realistic geometric mean is a little under 2x. Small benchmarks inevitably flatter the sorts of techniques I’ll be talking about, so “real” programs will on average see less than that .

What might surprise you is that we only added about 400LoC to PUC Lua, and changed under 50LoC. This hopefully makes the trade-off yk offers clear: yklua does not reach the performance peaks of the wonderful, carefully hand-written, LuaJIT; but LuaJIT is stuck in the 13 year old past of Lua 5.1 , whereas yklua trivially tracks updates to PUC Lua. The point of this post isn’t to tell you that one side or the other of this trade-off is best. Rather, it’s to show that the trade-off exists, and that yk fills in a part of the design space that was largely out of bounds before.

There’s also nothing inherently Lua specific about yk. Alongside various smaller implementations, recently we’ve put a few days effort into adjusting MicroPython leading to – you might spot a naming pattern here – ykmicropython. The MicroPython interpreter uses some idioms that we have yet to support, and any benchmark which encounters one of those currently has terrible performance. But if you have a program which doesn’t hit many of those parts you can already see a meaningful performance improvement. The Mandelbrot benchmark isn’t where ykmicropython performs best, but it’s still good enough to see a difference (“normal” MicroPython top, ykmicropython bottom):

[Video]

Eventually, my hope is that if you have a C interpreter, big or small, yk will allow you to add a JIT to it for little effort, gaining additional performance for free.

In this post I’ll explain why we created yk, why it works the way it does, and some of the technical challenges involved. Despite the length of this post, I won’t come close to covering everything I arguably should, and I’ll inevitably simplify much of what I do cover. Future posts will hopefully fill in more details, add nuance, and track yk’s evolution.

... continue reading