Elektrine lite

← Feed

@ltratt@mastodon.social

Post #2707873

2026-04-15 11:51 UTC

This isn't just a technique for Lua, though -- it works for any C interpreter compilable with LLVM! More about how and why in this new post 'Retrofitting JIT Compilers into C Interpreters' looking at our new 'yk' system. https://tratt.net/laurie/blog/2026/retrofitting_jit_compilers_into_c_interpreters.html

Replies (6)

  • @ltratt@mastodon.social This hopefully makes the trade-off yk offers clear: yklua does not reach the performance peaks of the wonderful, carefully hand-written, LuaJIT For what it's worth: In igk, I use sol3, which lets you select the Lua implementation as a build-time option. I don't use any fancy new Lua features in this (64-bit integers are really important for some other things where I looked at Lua, but not for igk), so I tried both Lua and LuaJIT. There wasn't much difference in terms of performance, but LuaJIT was a bit slower than the interpreter. My guess is that this is primarily because FFI is slower with LuaJIT and my code did a lot of FFI (basically everything it's doing is calling back into C++ to manipulate the text tree). I presume that yklua uses exactly the same memory layout as the C version, so I'd expect it to be better here. This is also a problem with a lot of Python JITs: If you make Python faster and make CPython-compatible FFI slower, you generally make Python programs slower.

    Open ##2707875

  • @llimllib@hachyderm.io 2026-04-15 12:06

    @ltratt@mastodon.social the videos in your post are not working on my phone (Cool work!)

    Open ##2707877

  • @ltratt@mastodon.social There's another approach that's worth mentioning, popularised by Apple's old shader JIT, which looks like a more ad-hoc version of what you've built. Each operation was written as a function that took a pointer to the interpreter state and updated it. The interpreter is then a big switch statement calling these functions. These typically all get inlined so you end up with one massive function that runs in a loop. To build the JIT, you compile those individual functions to LLVM IR, then JIT compile a function that is equivalent to the calls of a sequence of bytecode. The normal LLVM optimisers can then inline small or infrequently-used opcode bodies, and optimise across the whole program (or whole function, trace, or whatever else you want to JIT). The JIT'd code has the same interpreter state (though may update it only at the end of a trace - apparently marking it as not-aliasing-anything gets you around 10% extra performance), so you can JIT whatever size fragment makes sense.

    Open ##2707891

  • @csepp@merveilles.town 2026-04-15 14:45

    @ltratt@mastodon.social Thanks, I couldn't focus on work because I had to finish reading this. :flan_heckk: But seriously, amazing work. :moomin_yay:

    Open ##2707893

  • @zardoz03@mastodon.online 2026-04-15 16:39

    @ltratt@mastodon.social before even reading that i can tell that this is some compiler compiler wizardry if i have ever heard it

    Open ##2707895

  • @ltratt@mastodon.social I see that the codebase is written mostly in rust. Any case of getting this thing in as a crate on crates.io for me to import and play around with?

    Open ##2707896