Sljit: Platform independent low-level JIT compiler
github.com
github.com
> Although sljit does not support higher level features such as automatic register allocation
I don't quite see how it can be architecture independent if it doesn't do register allocation. Does it use a small fixed amount of virtual registers which work on every target? Or does it spill virtual registers to memory if required?
> The key design principle of sljit is that it does not try to be smarter than the developer.
> This principle is achieved by providing control over the generated machine code like assembly languages.
So it sounds like this is essentially your LLVM backend, taking care of going from intermediate representation to machine code.
Optimisations have to be done separately.
I see how a lightweight code generator could be quite useful, is sljit used in any larger projects?
The "LIR representation" itself is machine independent, but a given stream of LIR instructions won't necessarily be portable. (If I'm understanding correctly)
If it's low level and platform independent, that probably means more that it provides the tools to the user to work on many platforms, rather than it does the work for many platforms for the user.
Jit libraries like this are building blocks, not a turnkey interpreter to native execution pipeline.
This is pretty common in low tech compilers as well. Basically you assume just a few registers, and have some templates for various code sequences that compute a result and push it onto a stack, or that sort of thing. The code you get is way less efficient than an optimizing compiler would produce, but it beats most interpreters. GNU Lightning is a similar sort of JIT compiler.
The engine strikes a good balance between performance and
maintainability. The LIR code can be compiled to many CPU
architectures, and the performance of the generated code
is very close to code written in assembly languages.
Although sljit does not support higher level features
such as automatic register allocation, it can be a code
generator backend for other JIT compiler libraries.
Developing these intermediate libraries takes far
less time, because they only needs to support a single
backend.
https://zherczeg.github.io/sljit/I'd love to see some examples of other projects incorporating this library.
https://github.com/zherczeg/sljit/blob/master/sljit_src/slji...
(Lines 167-205):
// Scratch registers.
#define SLJIT_R0 1
#define SLJIT_R1 2
#define SLJIT_R2 3
// Note: on x86-32, R3 - R6 (same as S3 - S6) are emulated (they are allocated on the stack). These registers are called virtual and cannot be used for memory addressing (cannot be part of any SLJIT_MEM1, SLJIT_MEM2 construct). There is no such limitation on other CPUs. See sljit_get_register_index().
#define SLJIT_R3 4
[...]
#define SLJIT_R9 10
[...]
/* Saved registers. */
#define SLJIT_S0 (SLJIT_NUMBER_OF_REGISTERS)
#define SLJIT_S1 (SLJIT_NUMBER_OF_REGISTERS - 1)
#define SLJIT_S2 (SLJIT_NUMBER_OF_REGISTERS - 2)
// Note: on x86-32, S3 - S6 (same as R3 - R6) are emulated (they are allocated on the stack). These registers are called virtual and cannot be used for memory addressing (cannot be part of any SLJIT_MEM1, SLJIT_MEM2 construct). There is no such limitation on other CPUs. See sljit_get_register_index().
#define SLJIT_S3 (SLJIT_NUMBER_OF_REGISTERS - 3)
[...]
#define SLJIT_S9 (SLJIT_NUMBER_OF_REGISTERS - 9)
Anyway, this is a cool technique...
Emulating additional registers on machines that don't have them via the Stack, so that the assembly-like LIR can run on those machines too!
Very nice!
It looks like you have to do a function call for every high level assembly instruction which in turn does quite a bit of work. See `emit_x86_instruction`[2], most of which is redundant and most of which can be done ahead of time. To JIT quickly you want to work with templates if at all possible. Precompile those templates for the relevant architecture. Then at runtime you just patch the precompiled machine code with the correct addresses and registers. This extra speed really matters because if JITing code is cheap you can compile functions multiple times, inline aggressively and do many other optimizations that wouldn't be economical otherwise.
[1] https://github.com/zherczeg/sljit/blob/master/test_src/sljit...
[2]: https://github.com/zherczeg/sljit/blob/master/sljit_src/slji...
Let Common Sense reign. If the target Plattform I remains abstract, it will be a generic machine code translation. No way for sensible performance.
I would love to see a Lua transpiler.
PCRE2's jit engine, which is powered by sljit, is one of the fastest regex engines in existence (according to my own benchmarks): https://github.com/BurntSushi/rebar?tab=readme-ov-file#summa...